From 828bc29de2b20a1d21d885024be3dd3a64cc98af Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 23 Jun 2022 16:40:38 +0200 Subject: [PATCH 01/90] status-bar: first_line: Use more generic var names Rename all `CtrlKey...` to the equivalent `Key...` to make the name less specific. It implies that all key bindings use Ctrl as modifier key, which needn't necessarily be the case. --- default-plugins/status-bar/src/first_line.rs | 226 ++++++++++--------- 1 file changed, 115 insertions(+), 111 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 6c10b8832c..b5aa347172 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -4,18 +4,12 @@ use zellij_tile::prelude::*; use crate::color_elements; use crate::{ColoredElements, LinePart}; -struct CtrlKeyShortcut { - mode: CtrlKeyMode, - action: CtrlKeyAction, +struct KeyShortcut { + mode: KeyMode, + action: KeyAction, } -impl CtrlKeyShortcut { - pub fn new(mode: CtrlKeyMode, action: CtrlKeyAction) -> Self { - CtrlKeyShortcut { mode, action } - } -} - -enum CtrlKeyAction { +enum KeyAction { Lock, Pane, Tab, @@ -26,36 +20,41 @@ enum CtrlKeyAction { Move, } -enum CtrlKeyMode { +enum KeyMode { Unselected, UnselectedAlternate, Selected, Disabled, } -impl CtrlKeyShortcut { +impl KeyShortcut { + //pub fn new(mode: KeyMode, action: KeyAction, bind: KeyBind) -> Self { + pub fn new(mode: KeyMode, action: KeyAction) -> Self { + KeyShortcut { mode, action } //, bind } + } + pub fn full_text(&self) -> String { match self.action { - CtrlKeyAction::Lock => String::from("LOCK"), - CtrlKeyAction::Pane => String::from("PANE"), - CtrlKeyAction::Tab => String::from("TAB"), - CtrlKeyAction::Resize => String::from("RESIZE"), - CtrlKeyAction::Search => String::from("SEARCH"), - CtrlKeyAction::Quit => String::from("QUIT"), - CtrlKeyAction::Session => String::from("SESSION"), - CtrlKeyAction::Move => String::from("MOVE"), + KeyAction::Lock => String::from("LOCK"), + KeyAction::Pane => String::from("PANE"), + KeyAction::Tab => String::from("TAB"), + KeyAction::Resize => String::from("RESIZE"), + KeyAction::Search => String::from("SEARCH"), + KeyAction::Quit => String::from("QUIT"), + KeyAction::Session => String::from("SESSION"), + KeyAction::Move => String::from("MOVE"), } } pub fn letter_shortcut(&self) -> char { match self.action { - CtrlKeyAction::Lock => 'g', - CtrlKeyAction::Pane => 'p', - CtrlKeyAction::Tab => 't', - CtrlKeyAction::Resize => 'n', - CtrlKeyAction::Search => 's', - CtrlKeyAction::Quit => 'q', - CtrlKeyAction::Session => 'o', - CtrlKeyAction::Move => 'h', + KeyAction::Lock => 'g', + KeyAction::Pane => 'p', + KeyAction::Tab => 't', + KeyAction::Resize => 'n', + KeyAction::Search => 's', + KeyAction::Quit => 'q', + KeyAction::Session => 'o', + KeyAction::Move => 'h', } } } @@ -222,29 +221,29 @@ fn unselected_alternate_mode_shortcut_single_letter( } } -fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements, separator: &str) -> LinePart { +fn full_ctrl_key(key: &KeyShortcut, palette: ColoredElements, separator: &str) -> LinePart { let full_text = key.full_text(); let letter_shortcut = key.letter_shortcut(); match key.mode { - CtrlKeyMode::Unselected => unselected_mode_shortcut( + KeyMode::Unselected => unselected_mode_shortcut( letter_shortcut, &format!(" {}", full_text), palette, separator, ), - CtrlKeyMode::UnselectedAlternate => unselected_alternate_mode_shortcut( + KeyMode::UnselectedAlternate => unselected_alternate_mode_shortcut( letter_shortcut, &format!(" {}", full_text), palette, separator, ), - CtrlKeyMode::Selected => selected_mode_shortcut( + KeyMode::Selected => selected_mode_shortcut( letter_shortcut, &format!(" {}", full_text), palette, separator, ), - CtrlKeyMode::Disabled => disabled_mode_shortcut( + KeyMode::Disabled => disabled_mode_shortcut( &format!(" <{}> {}", letter_shortcut, full_text), palette, separator, @@ -253,22 +252,22 @@ fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements, separator: &st } fn single_letter_ctrl_key( - key: &CtrlKeyShortcut, + key: &KeyShortcut, palette: ColoredElements, separator: &str, ) -> LinePart { let letter_shortcut = key.letter_shortcut(); match key.mode { - CtrlKeyMode::Unselected => { + KeyMode::Unselected => { unselected_mode_shortcut_single_letter(letter_shortcut, palette, separator) }, - CtrlKeyMode::UnselectedAlternate => { + KeyMode::UnselectedAlternate => { unselected_alternate_mode_shortcut_single_letter(letter_shortcut, palette, separator) }, - CtrlKeyMode::Selected => { + KeyMode::Selected => { selected_mode_shortcut_single_letter(letter_shortcut, palette, separator) }, - CtrlKeyMode::Disabled => { + KeyMode::Disabled => { disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette, separator) }, } @@ -276,10 +275,11 @@ fn single_letter_ctrl_key( fn key_indicators( max_len: usize, - keys: &[CtrlKeyShortcut], + keys: &[KeyShortcut], palette: ColoredElements, separator: &str, ) -> LinePart { + // Print full-width hints let mut line_part = LinePart::default(); for ctrl_key in keys { let key = full_ctrl_key(ctrl_key, palette, separator); @@ -289,6 +289,8 @@ fn key_indicators( if line_part.len < max_len { return line_part; } + + // Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions) line_part = LinePart::default(); for ctrl_key in keys { let key = single_letter_ctrl_key(ctrl_key, palette, separator); @@ -298,6 +300,8 @@ fn key_indicators( if line_part.len < max_len { return line_part; } + + // Shortened doesn't fit, print nothing line_part = LinePart::default(); line_part } @@ -323,14 +327,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Locked => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Selected, KeyAction::Lock), + KeyShortcut::new(KeyMode::Disabled, KeyAction::Pane), + KeyShortcut::new(KeyMode::Disabled, KeyAction::Tab), + KeyShortcut::new(KeyMode::Disabled, KeyAction::Resize), + KeyShortcut::new(KeyMode::Disabled, KeyAction::Move), + KeyShortcut::new(KeyMode::Disabled, KeyAction::Search), + KeyShortcut::new(KeyMode::Disabled, KeyAction::Session), + KeyShortcut::new(KeyMode::Disabled, KeyAction::Quit), ], colored_elements, separator, @@ -338,14 +342,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Resize => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::Selected, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, @@ -353,14 +357,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Pane | InputMode::RenamePane => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::Selected, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, @@ -368,14 +372,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Tab | InputMode::RenameTab => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Selected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, @@ -383,14 +387,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::EnterSearch | InputMode::Scroll | InputMode::Search => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::Selected, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, @@ -398,14 +402,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Move => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Selected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, @@ -413,14 +417,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Normal | InputMode::Prompt => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, @@ -428,14 +432,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Session => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Selected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, @@ -443,14 +447,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { InputMode::Tmux => key_indicators( max_len, &[ - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Pane), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), ], colored_elements, separator, From a84319555a12aaa53854d46263fdf40a4d0cacfa Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 23 Jun 2022 16:44:37 +0200 Subject: [PATCH 02/90] status-bar: first_line: Refactor `ctrl_keys` Removes lots of code duplication by `Unselect`ing all keys by default and only `Select`ing what is actually required for a given Input mode. --- default-plugins/status-bar/src/first_line.rs | 164 ++++--------------- 1 file changed, 29 insertions(+), 135 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index b5aa347172..6af5014415 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -323,141 +323,35 @@ pub fn superkey(palette: ColoredElements, separator: &str) -> LinePart { pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { let supports_arrow_fonts = !help.capabilities.arrow_fonts; let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); + // Unselect all by default + let mut default_keys = [ + KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), + KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), + KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), + ]; + match &help.mode { - InputMode::Locked => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Selected, KeyAction::Lock), - KeyShortcut::new(KeyMode::Disabled, KeyAction::Pane), - KeyShortcut::new(KeyMode::Disabled, KeyAction::Tab), - KeyShortcut::new(KeyMode::Disabled, KeyAction::Resize), - KeyShortcut::new(KeyMode::Disabled, KeyAction::Move), - KeyShortcut::new(KeyMode::Disabled, KeyAction::Search), - KeyShortcut::new(KeyMode::Disabled, KeyAction::Session), - KeyShortcut::new(KeyMode::Disabled, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::Resize => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::Selected, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::Pane | InputMode::RenamePane => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::Selected, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::Tab | InputMode::RenameTab => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Selected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::EnterSearch | InputMode::Scroll | InputMode::Search => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::Selected, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::Move => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Selected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::Normal | InputMode::Prompt => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::Session => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Selected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), - InputMode::Tmux => key_indicators( - max_len, - &[ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), - ], - colored_elements, - separator, - ), + InputMode::Normal | InputMode::Prompt | InputMode::Tmux => (), + InputMode::Locked => { + default_keys[0].mode = KeyMode::Selected; + for key in default_keys.iter_mut().skip(1) { + key.mode = KeyMode::Disabled; + } + }, + InputMode::Pane | InputMode::RenamePane => default_keys[1].mode = KeyMode::Selected, + InputMode::Tab | InputMode::RenameTab => default_keys[2].mode = KeyMode::Selected, + InputMode::Resize => default_keys[3].mode = KeyMode::Selected, + InputMode::Move => default_keys[4].mode = KeyMode::Selected, + InputMode::Scroll | InputMode::Search | InputMode::EnterSearch => { + default_keys[5].mode = KeyMode::Selected + }, + InputMode::Session => default_keys[6].mode = KeyMode::Selected, } + + key_indicators(max_len, &default_keys, colored_elements, separator) } From 82a729a9b00c2e884c06ffc49aaa55888f96c0f7 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 27 Jun 2022 12:26:43 +0200 Subject: [PATCH 03/90] utils: conditionally compile unix-specific code In `zellij_utils`, the following modules each contained code that was previously targeting only the unix platform: - consts: Works with unix-specific filesystem attributes to set e.g. special file permissions. Also relies on having a UID. - shared: Uses unix-specific filesystem attributes to set file permissions These will never work when targeting wasm. Hence the concerning code passages have been moved into private submodules that are only compiled and re-exported when the target isn't `#[cfg(unix)]`. The re-export makes sure that crates from the outside that use `zellij_utils` work as before, since from their point of view nothing has changed. --- zellij-utils/src/consts.rs | 66 ++++++++++++++++++++++---------------- zellij-utils/src/shared.rs | 21 ++++++++---- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index 39d125c781..cb11315fc2 100644 --- a/zellij-utils/src/consts.rs +++ b/zellij-utils/src/consts.rs @@ -1,13 +1,9 @@ //! Zellij program-wide constants. -use crate::envs; -use crate::shared::set_permissions; use directories_next::ProjectDirs; use lazy_static::lazy_static; -use nix::unistd::Uid; use once_cell::sync::OnceCell; use std::path::PathBuf; -use std::{env, fs}; pub const ZELLIJ_CONFIG_FILE_ENV: &str = "ZELLIJ_CONFIG_FILE"; pub const ZELLIJ_CONFIG_DIR_ENV: &str = "ZELLIJ_CONFIG_DIR"; @@ -28,31 +24,8 @@ const fn system_default_data_dir() -> &'static str { } lazy_static! { - static ref UID: Uid = Uid::current(); pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - pub static ref ZELLIJ_SOCK_DIR: PathBuf = { - let mut ipc_dir = envs::get_socket_dir().map_or_else( - |_| { - ZELLIJ_PROJ_DIR - .runtime_dir() - .map_or_else(|| ZELLIJ_TMP_DIR.clone(), |p| p.to_owned()) - }, - PathBuf::from, - ); - ipc_dir.push(VERSION); - ipc_dir - }; - pub static ref ZELLIJ_IPC_PIPE: PathBuf = { - let mut sock_dir = ZELLIJ_SOCK_DIR.clone(); - fs::create_dir_all(&sock_dir).unwrap(); - set_permissions(&sock_dir, 0o700).unwrap(); - sock_dir.push(envs::get_session_name().unwrap()); - sock_dir - }; - pub static ref ZELLIJ_TMP_DIR: PathBuf = PathBuf::from(format!("/tmp/zellij-{}", *UID)); - pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); - pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log"); pub static ref ZELLIJ_CACHE_DIR: PathBuf = ZELLIJ_PROJ_DIR.cache_dir().to_path_buf(); } @@ -60,3 +33,42 @@ pub const FEATURES: &[&str] = &[ #[cfg(feature = "disable_automatic_asset_installation")] "disable_automatic_asset_installation", ]; + +#[cfg(unix)] +pub use unix_only::*; + +#[cfg(unix)] +mod unix_only { + use super::*; + use crate::envs; + use crate::shared::set_permissions; + use lazy_static::lazy_static; + use nix::unistd::Uid; + use std::fs; + + lazy_static! { + static ref UID: Uid = Uid::current(); + pub static ref ZELLIJ_IPC_PIPE: PathBuf = { + let mut sock_dir = ZELLIJ_SOCK_DIR.clone(); + fs::create_dir_all(&sock_dir).unwrap(); + set_permissions(&sock_dir, 0o700).unwrap(); + sock_dir.push(envs::get_session_name().unwrap()); + sock_dir + }; + pub static ref ZELLIJ_TMP_DIR: PathBuf = PathBuf::from(format!("/tmp/zellij-{}", *UID)); + pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); + pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log"); + pub static ref ZELLIJ_SOCK_DIR: PathBuf = { + let mut ipc_dir = envs::get_socket_dir().map_or_else( + |_| { + ZELLIJ_PROJ_DIR + .runtime_dir() + .map_or_else(|| ZELLIJ_TMP_DIR.clone(), |p| p.to_owned()) + }, + PathBuf::from, + ); + ipc_dir.push(VERSION); + ipc_dir + }; + } +} diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index b916c4119d..37d999d1a1 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -5,16 +5,23 @@ use std::{iter, str::from_utf8}; use crate::data::{Palette, PaletteColor, PaletteSource, ThemeHue}; use crate::envs::get_session_name; use colorsys::Rgb; -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use std::{fs, io}; use strip_ansi_escapes::strip; use unicode_width::UnicodeWidthStr; -pub fn set_permissions(path: &Path, mode: u32) -> io::Result<()> { - let mut permissions = fs::metadata(path)?.permissions(); - permissions.set_mode(mode); - fs::set_permissions(path, permissions) +#[cfg(unix)] +pub use unix_only::*; + +#[cfg(unix)] +mod unix_only { + use std::os::unix::fs::PermissionsExt; + use std::path::Path; + use std::{fs, io}; + + pub fn set_permissions(path: &Path, mode: u32) -> io::Result<()> { + let mut permissions = fs::metadata(path)?.permissions(); + permissions.set_mode(mode); + fs::set_permissions(path, permissions) + } } pub fn ansi_len(s: &str) -> usize { From 09c67069f3c36a9824bde078e798e5b14a642b22 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 27 Jun 2022 12:33:07 +0200 Subject: [PATCH 04/90] utils: Share more modules with wasm that work on both wasm and unix natively. This requires factoring out bits of code in the `setup` and `input` modules into a private submodule that is re-exported when the compilation target is *not* "wasm". The following modules are now available to the wasm target: - cli - consts - data - envs - input (partial) - actions - command - configs - keybinds - layout - options - plugins - theme - pane_size - position - setup (partial) - shared The remaining modules unavailable to wasm have dependencies on crates that cannot compile against wasm, such as `async_std` or `termwiz`. --- zellij-utils/src/input/mod.rs | 166 +++--- zellij-utils/src/lib.rs | 29 +- zellij-utils/src/setup.rs | 914 +++++++++++++++++----------------- 3 files changed, 564 insertions(+), 545 deletions(-) diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 268a4e2c87..8473a0eeef 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -1,21 +1,21 @@ //! The way terminal input is handled. - pub mod actions; pub mod command; pub mod config; pub mod keybinds; pub mod layout; -pub mod mouse; pub mod options; pub mod plugins; pub mod theme; -use super::{ - data::{CharOrArrow, Direction, Style}, - data::{InputMode, Key, ModeInfo, PluginCapabilities}, +// Can't use this in wasm due to dependency on the `termwiz` crate. +#[cfg(not(target_family = "wasm"))] +pub mod mouse; + +use crate::{ + envs, + data::{InputMode, Key, ModeInfo, PluginCapabilities, Style}, }; -use crate::envs; -use termwiz::input::{InputEvent, InputParser, KeyCode, KeyEvent, Modifiers}; /// Creates a [`ModeInfo`] struct indicating the current [`InputMode`] and its keybinds /// (as pairs of [`String`]s). @@ -98,79 +98,89 @@ pub fn get_mode_info(mode: InputMode, style: Style, capabilities: PluginCapabili } } -pub fn parse_keys(input_bytes: &[u8]) -> Vec { - let mut ret = vec![]; - let mut input_parser = InputParser::new(); // this is the termwiz InputParser - let maybe_more = false; - let parse_input_event = |input_event: InputEvent| { - if let InputEvent::Key(key_event) = input_event { - ret.push(cast_termwiz_key(key_event, input_bytes)); - } - }; - input_parser.parse(input_bytes, parse_input_event, maybe_more); - ret -} +#[cfg(not(target_family = "wasm"))] +pub use not_wasm::*; -// FIXME: This is an absolutely cursed function that should be destroyed as soon -// as an alternative that doesn't touch zellij-tile can be developed... -pub fn cast_termwiz_key(event: KeyEvent, raw_bytes: &[u8]) -> Key { - let modifiers = event.modifiers; - - // *** THIS IS WHERE WE SHOULD WORK AROUND ISSUES WITH TERMWIZ *** - if raw_bytes == [8] { - return Key::Ctrl('h'); - }; +#[cfg(not(target_family = "wasm"))] +mod not_wasm { + use super::*; + use crate::data::{CharOrArrow, Direction}; + use termwiz::input::{InputEvent, InputParser, KeyCode, KeyEvent, Modifiers}; - match event.key { - KeyCode::Char(c) => { - if modifiers.contains(Modifiers::CTRL) { - Key::Ctrl(c.to_lowercase().next().unwrap_or_default()) - } else if modifiers.contains(Modifiers::ALT) { - Key::Alt(CharOrArrow::Char(c)) - } else { - Key::Char(c) + pub fn parse_keys(input_bytes: &[u8]) -> Vec { + let mut ret = vec![]; + let mut input_parser = InputParser::new(); // this is the termwiz InputParser + let maybe_more = false; + let parse_input_event = |input_event: InputEvent| { + if let InputEvent::Key(key_event) = input_event { + ret.push(cast_termwiz_key(key_event, input_bytes)); } - }, - KeyCode::Backspace => Key::Backspace, - KeyCode::LeftArrow | KeyCode::ApplicationLeftArrow => { - if modifiers.contains(Modifiers::ALT) { - Key::Alt(CharOrArrow::Direction(Direction::Left)) - } else { - Key::Left - } - }, - KeyCode::RightArrow | KeyCode::ApplicationRightArrow => { - if modifiers.contains(Modifiers::ALT) { - Key::Alt(CharOrArrow::Direction(Direction::Right)) - } else { - Key::Right - } - }, - KeyCode::UpArrow | KeyCode::ApplicationUpArrow => { - if modifiers.contains(Modifiers::ALT) { - //Key::AltPlusUpArrow - Key::Alt(CharOrArrow::Direction(Direction::Up)) - } else { - Key::Up - } - }, - KeyCode::DownArrow | KeyCode::ApplicationDownArrow => { - if modifiers.contains(Modifiers::ALT) { - Key::Alt(CharOrArrow::Direction(Direction::Down)) - } else { - Key::Down - } - }, - KeyCode::Home => Key::Home, - KeyCode::End => Key::End, - KeyCode::PageUp => Key::PageUp, - KeyCode::PageDown => Key::PageDown, - KeyCode::Tab => Key::BackTab, // TODO: ??? - KeyCode::Delete => Key::Delete, - KeyCode::Insert => Key::Insert, - KeyCode::Function(n) => Key::F(n), - KeyCode::Escape => Key::Esc, - KeyCode::Enter => Key::Char('\n'), - _ => Key::Esc, // there are other keys we can implement here, but we might need additional terminal support to implement them, not just exhausting this enum + }; + input_parser.parse(input_bytes, parse_input_event, maybe_more); + ret + } + + // FIXME: This is an absolutely cursed function that should be destroyed as soon + // as an alternative that doesn't touch zellij-tile can be developed... + pub fn cast_termwiz_key(event: KeyEvent, raw_bytes: &[u8]) -> Key { + let modifiers = event.modifiers; + + // *** THIS IS WHERE WE SHOULD WORK AROUND ISSUES WITH TERMWIZ *** + if raw_bytes == [8] { + return Key::Ctrl('h'); + }; + + match event.key { + KeyCode::Char(c) => { + if modifiers.contains(Modifiers::CTRL) { + Key::Ctrl(c.to_lowercase().next().unwrap_or_default()) + } else if modifiers.contains(Modifiers::ALT) { + Key::Alt(CharOrArrow::Char(c)) + } else { + Key::Char(c) + } + }, + KeyCode::Backspace => Key::Backspace, + KeyCode::LeftArrow | KeyCode::ApplicationLeftArrow => { + if modifiers.contains(Modifiers::ALT) { + Key::Alt(CharOrArrow::Direction(Direction::Left)) + } else { + Key::Left + } + }, + KeyCode::RightArrow | KeyCode::ApplicationRightArrow => { + if modifiers.contains(Modifiers::ALT) { + Key::Alt(CharOrArrow::Direction(Direction::Right)) + } else { + Key::Right + } + }, + KeyCode::UpArrow | KeyCode::ApplicationUpArrow => { + if modifiers.contains(Modifiers::ALT) { + //Key::AltPlusUpArrow + Key::Alt(CharOrArrow::Direction(Direction::Up)) + } else { + Key::Up + } + }, + KeyCode::DownArrow | KeyCode::ApplicationDownArrow => { + if modifiers.contains(Modifiers::ALT) { + Key::Alt(CharOrArrow::Direction(Direction::Down)) + } else { + Key::Down + } + }, + KeyCode::Home => Key::Home, + KeyCode::End => Key::End, + KeyCode::PageUp => Key::PageUp, + KeyCode::PageDown => Key::PageDown, + KeyCode::Tab => Key::BackTab, // TODO: ??? + KeyCode::Delete => Key::Delete, + KeyCode::Insert => Key::Insert, + KeyCode::Function(n) => Key::F(n), + KeyCode::Escape => Key::Esc, + KeyCode::Enter => Key::Char('\n'), + _ => Key::Esc, // there are other keys we can implement here, but we might need additional terminal support to implement them, not just exhausting this enum + } } } diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs index 94c4f18dbf..3cff1f046a 100644 --- a/zellij-utils/src/lib.rs +++ b/zellij-utils/src/lib.rs @@ -1,30 +1,23 @@ -pub mod data; - -#[cfg(not(target_family = "wasm"))] -pub mod channels; -#[cfg(not(target_family = "wasm"))] pub mod cli; -#[cfg(not(target_family = "wasm"))] pub mod consts; -#[cfg(not(target_family = "wasm"))] +pub mod data; pub mod envs; -#[cfg(not(target_family = "wasm"))] -pub mod errors; -#[cfg(not(target_family = "wasm"))] pub mod input; -#[cfg(not(target_family = "wasm"))] -pub mod ipc; -#[cfg(not(target_family = "wasm"))] -pub mod logging; -#[cfg(not(target_family = "wasm"))] pub mod pane_size; -#[cfg(not(target_family = "wasm"))] pub mod position; -#[cfg(not(target_family = "wasm"))] pub mod setup; -#[cfg(not(target_family = "wasm"))] pub mod shared; +// The following modules can't be used when targeting wasm +#[cfg(not(target_family = "wasm"))] +pub mod channels; // Requires async_std +#[cfg(not(target_family = "wasm"))] +pub mod errors; // Requires async_std (via channels) +#[cfg(not(target_family = "wasm"))] +pub mod ipc; // Requires interprocess +#[cfg(not(target_family = "wasm"))] +pub mod logging; // Requires log4rs + #[cfg(not(target_family = "wasm"))] pub use ::{ anyhow, async_std, clap, interprocess, lazy_static, libc, nix, regex, serde, serde_yaml, diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index 209dc18975..17e2f3145f 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -1,94 +1,13 @@ -use crate::{ - cli::{CliArgs, Command}, - consts::{ - FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, - ZELLIJ_PROJ_DIR, - }, - input::{ - config::{Config, ConfigError}, - layout::{LayoutFromYaml, LayoutFromYamlIntermediate}, - options::Options, - theme::ThemesFromYaml, - }, -}; -use clap::{Args, IntoApp}; -use clap_complete::Shell; +use crate::consts::{SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, ZELLIJ_PROJ_DIR}; +use clap::Args; use directories_next::BaseDirs; use serde::{Deserialize, Serialize}; use std::{ - convert::TryFrom, fmt::Write as FmtWrite, io::Write, path::Path, path::PathBuf, process, + io::Write, + path::{Path, PathBuf}, }; const CONFIG_LOCATION: &str = ".config/zellij"; -const CONFIG_NAME: &str = "config.yaml"; -static ARROW_SEPARATOR: &str = ""; - -#[cfg(not(test))] -/// Goes through a predefined list and checks for an already -/// existing config directory, returns the first match -pub fn find_default_config_dir() -> Option { - default_config_dirs() - .into_iter() - .filter(|p| p.is_some()) - .find(|p| p.clone().unwrap().exists()) - .flatten() -} - -#[cfg(test)] -pub fn find_default_config_dir() -> Option { - None -} - -/// Order in which config directories are checked -fn default_config_dirs() -> Vec> { - vec![ - home_config_dir(), - Some(xdg_config_dir()), - Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), - ] -} - -/// Looks for an existing dir, uses that, else returns a -/// dir matching the config spec. -pub fn get_default_data_dir() -> PathBuf { - [ - xdg_data_dir(), - Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"), - ] - .into_iter() - .find(|p| p.exists()) - .unwrap_or_else(xdg_data_dir) -} - -pub fn xdg_config_dir() -> PathBuf { - ZELLIJ_PROJ_DIR.config_dir().to_owned() -} - -pub fn xdg_data_dir() -> PathBuf { - ZELLIJ_PROJ_DIR.data_dir().to_owned() -} - -pub fn home_config_dir() -> Option { - if let Some(user_dirs) = BaseDirs::new() { - let config_dir = user_dirs.home_dir().join(CONFIG_LOCATION); - Some(config_dir) - } else { - None - } -} - -pub fn get_layout_dir(config_dir: Option) -> Option { - config_dir.map(|dir| dir.join("layouts")) -} - -pub fn get_theme_dir(config_dir: Option) -> Option { - config_dir.map(|dir| dir.join("themes")) -} - -pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { - std::io::stdout().write_all(asset)?; - Ok(()) -} pub const DEFAULT_CONFIG: &[u8] = include_bytes!(concat!( env!("CARGO_MANIFEST_DIR"), @@ -144,6 +63,15 @@ pub const ZSH_AUTO_START_SCRIPT: &[u8] = include_bytes!(concat!( "assets/shell/auto-start.zsh" )); +pub fn get_theme_dir(config_dir: Option) -> Option { + config_dir.map(|dir| dir.join("themes")) +} + +pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { + std::io::stdout().write_all(asset)?; + Ok(()) +} + pub fn dump_default_config() -> std::io::Result<()> { dump_asset(DEFAULT_CONFIG) } @@ -190,413 +118,501 @@ pub struct Setup { pub generate_auto_start: Option, } -impl Setup { - /// Entrypoint from main - /// Merges options from the config file and the command line options - /// into `[Options]`, the command line options superceeding the layout - /// file options, superceeding the config file options: - /// 1. command line options (`zellij options`) - /// 2. layout options - /// (`layout.yaml` / `zellij --layout`) - /// 3. config options (`config.yaml`) - pub fn from_options( - opts: &CliArgs, - ) -> Result<(Config, Option, Options), ConfigError> { - let clean = match &opts.command { - Some(Command::Setup(ref setup)) => setup.clean, - _ => false, - }; +#[cfg(test)] +pub fn find_default_config_dir() -> Option { + None +} - // setup functions that don't require deserialisation of the config - if let Some(Command::Setup(ref setup)) = &opts.command { - setup.from_cli().map_or_else( - |e| { - eprintln!("{:?}", e); - process::exit(1); - }, - |_| {}, - ); - }; +#[cfg(not(test))] +/// Goes through a predefined list and checks for an already +/// existing config directory, returns the first match +pub fn find_default_config_dir() -> Option { + default_config_dirs() + .into_iter() + .filter(|p| p.is_some()) + .find(|p| p.clone().unwrap().exists()) + .flatten() +} - let mut config = if !clean { - match Config::try_from(opts) { - Ok(config) => config, - Err(e) => { - return Err(e); - }, - } - } else { - Config::default() - }; +/// Order in which config directories are checked +#[allow(dead_code)] +fn default_config_dirs() -> Vec> { + vec![ + home_config_dir(), + Some(xdg_config_dir()), + Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), + ] +} - let config_options = Options::from_cli(&config.options, opts.command.clone()); - - let layout_dir = config_options - .layout_dir - .clone() - .or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir))); - let chosen_layout = opts - .layout - .clone() - .or_else(|| config_options.default_layout.clone()); - let layout_result = - LayoutFromYamlIntermediate::from_path_or_default(chosen_layout.as_ref(), layout_dir); - let layout = match layout_result { - None => None, - Some(Ok(layout)) => Some(layout), - Some(Err(e)) => { - return Err(e); - }, - }; +/// Looks for an existing dir, uses that, else returns a +/// dir matching the config spec. +pub fn get_default_data_dir() -> PathBuf { + [ + xdg_data_dir(), + Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"), + ] + .into_iter() + .find(|p| p.exists()) + .unwrap_or_else(xdg_data_dir) +} - if let Some(theme_dir) = config_options - .theme_dir - .clone() - .or_else(|| get_theme_dir(opts.config_dir.clone().or_else(find_default_config_dir))) - { - if theme_dir.is_dir() { - for entry in (theme_dir.read_dir()?).flatten() { - if let Some(extension) = entry.path().extension() { - if extension == "yaml" || extension == "yml" { - if let Ok(themes) = ThemesFromYaml::from_path(&entry.path()) { - config.themes = config.themes.map(|t| t.merge(themes.into())); - } - } - } - } - } - } +pub fn xdg_config_dir() -> PathBuf { + ZELLIJ_PROJ_DIR.config_dir().to_owned() +} - if let Some(Command::Setup(ref setup)) = &opts.command { - setup - .from_cli_with_options(opts, &config_options) - .map_or_else( +pub fn xdg_data_dir() -> PathBuf { + ZELLIJ_PROJ_DIR.data_dir().to_owned() +} + +pub fn home_config_dir() -> Option { + if let Some(user_dirs) = BaseDirs::new() { + let config_dir = user_dirs.home_dir().join(CONFIG_LOCATION); + Some(config_dir) + } else { + None + } +} + +pub fn get_layout_dir(config_dir: Option) -> Option { + config_dir.map(|dir| dir.join("layouts")) +} + +#[cfg(not(target_family = "wasm"))] +pub use not_wasm::*; + +#[cfg(not(target_family = "wasm"))] +mod not_wasm { + use super::*; + use crate::{ + cli::{CliArgs, Command}, + consts::{FEATURES, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION}, + input::{ + config::{Config, ConfigError}, + layout::{LayoutFromYaml, LayoutFromYamlIntermediate}, + options::Options, + theme::ThemesFromYaml, + }, + }; + use clap::IntoApp; + use clap_complete::Shell; + use std::{convert::TryFrom, fmt::Write as FmtWrite, io::Write, path::PathBuf, process}; + + const CONFIG_NAME: &str = "config.yaml"; + static ARROW_SEPARATOR: &str = ""; + + impl Setup { + /// Entrypoint from main + /// Merges options from the config file and the command line options + /// into `[Options]`, the command line options superceeding the layout + /// file options, superceeding the config file options: + /// 1. command line options (`zellij options`) + /// 2. layout options + /// (`layout.yaml` / `zellij --layout`) + /// 3. config options (`config.yaml`) + pub fn from_options( + opts: &CliArgs, + ) -> Result<(Config, Option, Options), ConfigError> { + let clean = match &opts.command { + Some(Command::Setup(ref setup)) => setup.clean, + _ => false, + }; + + // setup functions that don't require deserialisation of the config + if let Some(Command::Setup(ref setup)) = &opts.command { + setup.from_cli().map_or_else( |e| { eprintln!("{:?}", e); process::exit(1); }, |_| {}, ); - }; + }; - Setup::merge_config_with_layout(config, layout, config_options) - } + let mut config = if !clean { + match Config::try_from(opts) { + Ok(config) => config, + Err(e) => { + return Err(e); + }, + } + } else { + Config::default() + }; - /// General setup helpers - pub fn from_cli(&self) -> std::io::Result<()> { - if self.clean { - return Ok(()); - } + let config_options = Options::from_cli(&config.options, opts.command.clone()); + + let layout_dir = config_options.layout_dir.clone().or_else(|| { + get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir)) + }); + let chosen_layout = opts + .layout + .clone() + .or_else(|| config_options.default_layout.clone()); + let layout_result = LayoutFromYamlIntermediate::from_path_or_default( + chosen_layout.as_ref(), + layout_dir, + ); + let layout = match layout_result { + None => None, + Some(Ok(layout)) => Some(layout), + Some(Err(e)) => { + return Err(e); + }, + }; - if self.dump_config { - dump_default_config()?; - std::process::exit(0); - } + if let Some(theme_dir) = config_options + .theme_dir + .clone() + .or_else(|| get_theme_dir(opts.config_dir.clone().or_else(find_default_config_dir))) + { + if theme_dir.is_dir() { + for entry in (theme_dir.read_dir()?).flatten() { + if let Some(extension) = entry.path().extension() { + if extension == "yaml" || extension == "yml" { + if let Ok(themes) = ThemesFromYaml::from_path(&entry.path()) { + config.themes = config.themes.map(|t| t.merge(themes.into())); + } + } + } + } + } + } - if let Some(shell) = &self.generate_completion { - Self::generate_completion(shell); - std::process::exit(0); - } + if let Some(Command::Setup(ref setup)) = &opts.command { + setup + .from_cli_with_options(opts, &config_options) + .map_or_else( + |e| { + eprintln!("{:?}", e); + process::exit(1); + }, + |_| {}, + ); + }; - if let Some(shell) = &self.generate_auto_start { - Self::generate_auto_start(shell); - std::process::exit(0); + Setup::merge_config_with_layout(config, layout, config_options) } - if let Some(layout) = &self.dump_layout { - dump_specified_layout(layout)?; - std::process::exit(0); - } + /// General setup helpers + pub fn from_cli(&self) -> std::io::Result<()> { + if self.clean { + return Ok(()); + } - Ok(()) - } + if self.dump_config { + dump_default_config()?; + std::process::exit(0); + } - /// Checks the merged configuration - pub fn from_cli_with_options( - &self, - opts: &CliArgs, - config_options: &Options, - ) -> std::io::Result<()> { - if self.check { - Setup::check_defaults_config(opts, config_options)?; - std::process::exit(0); + if let Some(shell) = &self.generate_completion { + Self::generate_completion(shell); + std::process::exit(0); + } + + if let Some(shell) = &self.generate_auto_start { + Self::generate_auto_start(shell); + std::process::exit(0); + } + + if let Some(layout) = &self.dump_layout { + dump_specified_layout(layout)?; + std::process::exit(0); + } + + Ok(()) } - Ok(()) - } - fn merge_config_with_layout( - config: Config, - layout: Option, - config_options: Options, - ) -> Result<(Config, Option, Options), ConfigError> { - let (layout, layout_config) = match layout.map(|l| l.to_layout_and_config()) { - None => (None, None), - Some((layout, layout_config)) => (Some(layout), layout_config), - }; + /// Checks the merged configuration + pub fn from_cli_with_options( + &self, + opts: &CliArgs, + config_options: &Options, + ) -> std::io::Result<()> { + if self.check { + Setup::check_defaults_config(opts, config_options)?; + std::process::exit(0); + } + Ok(()) + } + + fn merge_config_with_layout( + config: Config, + layout: Option, + config_options: Options, + ) -> Result<(Config, Option, Options), ConfigError> { + let (layout, layout_config) = match layout.map(|l| l.to_layout_and_config()) { + None => (None, None), + Some((layout, layout_config)) => (Some(layout), layout_config), + }; - let (config, config_options) = if let Some(layout_config) = layout_config { - let config_options = if let Some(options) = layout_config.options.clone() { - config_options.merge(options) + let (config, config_options) = if let Some(layout_config) = layout_config { + let config_options = if let Some(options) = layout_config.options.clone() { + config_options.merge(options) + } else { + config_options + }; + let config = config.merge(layout_config.try_into()?); + (config, config_options) } else { - config_options + (config, config_options) }; - let config = config.merge(layout_config.try_into()?); - (config, config_options) - } else { - (config, config_options) - }; - Ok((config, layout, config_options)) - } + Ok((config, layout, config_options)) + } - pub fn check_defaults_config(opts: &CliArgs, config_options: &Options) -> std::io::Result<()> { - let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir); - let config_dir = opts.config_dir.clone().or_else(find_default_config_dir); - let plugin_dir = data_dir.join("plugins"); - let layout_dir = config_options - .layout_dir - .clone() - .or_else(|| get_layout_dir(config_dir.clone())); - let theme_dir = config_options - .theme_dir - .clone() - .or_else(|| get_theme_dir(config_dir.clone())); - let system_data_dir = PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"); - let config_file = opts - .config - .clone() - .or_else(|| config_dir.clone().map(|p| p.join(CONFIG_NAME))); - - // according to - // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda - let hyperlink_start = "\u{1b}]8;;"; - let hyperlink_mid = "\u{1b}\\"; - let hyperlink_end = "\u{1b}]8;;\u{1b}\\"; - - let mut message = String::new(); - - writeln!(&mut message, "[Version]: {:?}", VERSION).unwrap(); - if let Some(config_dir) = config_dir { - writeln!(&mut message, "[CONFIG DIR]: {:?}", config_dir).unwrap(); - } else { - message.push_str("[CONFIG DIR]: Not Found\n"); - let mut default_config_dirs = default_config_dirs() - .iter() - .filter_map(|p| p.clone()) - .collect::>(); - default_config_dirs.dedup(); - message.push_str( - " On your system zellij looks in the following config directories by default:\n", - ); - for dir in default_config_dirs { - writeln!(&mut message, " {:?}", dir).unwrap(); + pub fn check_defaults_config( + opts: &CliArgs, + config_options: &Options, + ) -> std::io::Result<()> { + let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir); + let config_dir = opts.config_dir.clone().or_else(find_default_config_dir); + let plugin_dir = data_dir.join("plugins"); + let layout_dir = config_options + .layout_dir + .clone() + .or_else(|| get_layout_dir(config_dir.clone())); + let theme_dir = config_options + .theme_dir + .clone() + .or_else(|| get_theme_dir(config_dir.clone())); + let system_data_dir = + PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"); + let config_file = opts + .config + .clone() + .or_else(|| config_dir.clone().map(|p| p.join(CONFIG_NAME))); + + // according to + // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + let hyperlink_start = "\u{1b}]8;;"; + let hyperlink_mid = "\u{1b}\\"; + let hyperlink_end = "\u{1b}]8;;\u{1b}\\"; + + let mut message = String::new(); + + writeln!(&mut message, "[Version]: {:?}", VERSION).unwrap(); + if let Some(config_dir) = config_dir { + writeln!(&mut message, "[CONFIG DIR]: {:?}", config_dir).unwrap(); + } else { + message.push_str("[CONFIG DIR]: Not Found\n"); + let mut default_config_dirs = default_config_dirs() + .iter() + .filter_map(|p| p.clone()) + .collect::>(); + default_config_dirs.dedup(); + message.push_str( + " On your system zellij looks in the following config directories by default:\n", + ); + for dir in default_config_dirs { + writeln!(&mut message, " {:?}", dir).unwrap(); + } } - } - if let Some(config_file) = config_file { - writeln!(&mut message, "[CONFIG FILE]: {:?}", config_file).unwrap(); - match Config::new(&config_file) { - Ok(_) => message.push_str("[CONFIG FILE]: Well defined.\n"), - Err(e) => writeln!(&mut message, "[CONFIG ERROR]: {}", e).unwrap(), + if let Some(config_file) = config_file { + writeln!(&mut message, "[CONFIG FILE]: {:?}", config_file).unwrap(); + match Config::new(&config_file) { + Ok(_) => message.push_str("[CONFIG FILE]: Well defined.\n"), + Err(e) => writeln!(&mut message, "[CONFIG ERROR]: {}", e).unwrap(), + } + } else { + message.push_str("[CONFIG FILE]: Not Found\n"); + writeln!( + &mut message, + " By default zellij looks for a file called [{}] in the configuration directory", + CONFIG_NAME + ) + .unwrap(); } - } else { - message.push_str("[CONFIG FILE]: Not Found\n"); - writeln!( + writeln!(&mut message, "[DATA DIR]: {:?}", data_dir).unwrap(); + message.push_str(&format!("[PLUGIN DIR]: {:?}\n", plugin_dir)); + if let Some(layout_dir) = layout_dir { + writeln!(&mut message, "[LAYOUT DIR]: {:?}", layout_dir).unwrap(); + } else { + message.push_str("[LAYOUT DIR]: Not Found\n"); + } + if let Some(theme_dir) = theme_dir { + writeln!(&mut message, "[THEME DIR]: {:?}", theme_dir).unwrap(); + } else { + message.push_str("[THEME DIR]: Not Found\n"); + } + writeln!(&mut message, "[SYSTEM DATA DIR]: {:?}", system_data_dir).unwrap(); + + writeln!(&mut message, "[ARROW SEPARATOR]: {}", ARROW_SEPARATOR).unwrap(); + message.push_str(" Is the [ARROW_SEPARATOR] displayed correctly?\n"); + message.push_str(" If not you may want to either start zellij with a compatible mode: 'zellij options --simplified-ui true'\n"); + let mut hyperlink_compat = String::new(); + hyperlink_compat.push_str(hyperlink_start); + hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly"); + hyperlink_compat.push_str(hyperlink_mid); + hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly"); + hyperlink_compat.push_str(hyperlink_end); + write!( &mut message, - " By default zellij looks for a file called [{}] in the configuration directory", - CONFIG_NAME + " Or check the font that is in use:\n {}\n", + hyperlink_compat ) .unwrap(); + message.push_str("[MOUSE INTERACTION]: \n"); + message.push_str(" Can be temporarily disabled through pressing the [SHIFT] key.\n"); + message.push_str(" If that doesn't fix any issues consider to disable the mouse handling of zellij: 'zellij options --disable-mouse-mode'\n"); + + let default_editor = std::env::var("EDITOR") + .or_else(|_| std::env::var("VISUAL")) + .unwrap_or_else(|_| String::from("Not set, checked $EDITOR and $VISUAL")); + writeln!(&mut message, "[DEFAULT EDITOR]: {}", default_editor).unwrap(); + writeln!(&mut message, "[FEATURES]: {:?}", FEATURES).unwrap(); + let mut hyperlink = String::new(); + hyperlink.push_str(hyperlink_start); + hyperlink.push_str("https://www.zellij.dev/documentation/"); + hyperlink.push_str(hyperlink_mid); + hyperlink.push_str("zellij.dev/documentation"); + hyperlink.push_str(hyperlink_end); + writeln!(&mut message, "[DOCUMENTATION]: {}", hyperlink).unwrap(); + //printf '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n' + + std::io::stdout().write_all(message.as_bytes())?; + + Ok(()) } - writeln!(&mut message, "[DATA DIR]: {:?}", data_dir).unwrap(); - message.push_str(&format!("[PLUGIN DIR]: {:?}\n", plugin_dir)); - if let Some(layout_dir) = layout_dir { - writeln!(&mut message, "[LAYOUT DIR]: {:?}", layout_dir).unwrap(); - } else { - message.push_str("[LAYOUT DIR]: Not Found\n"); - } - if let Some(theme_dir) = theme_dir { - writeln!(&mut message, "[THEME DIR]: {:?}", theme_dir).unwrap(); - } else { - message.push_str("[THEME DIR]: Not Found\n"); + fn generate_completion(shell: &str) { + let shell: Shell = match shell.to_lowercase().parse() { + Ok(shell) => shell, + _ => { + eprintln!("Unsupported shell: {}", shell); + std::process::exit(1); + }, + }; + let mut out = std::io::stdout(); + clap_complete::generate(shell, &mut CliArgs::command(), "zellij", &mut out); + // add shell dependent extra completion + match shell { + Shell::Bash => {}, + Shell::Elvish => {}, + Shell::Fish => { + let _ = out.write_all(FISH_EXTRA_COMPLETION); + }, + Shell::PowerShell => {}, + Shell::Zsh => {}, + _ => {}, + }; } - writeln!(&mut message, "[SYSTEM DATA DIR]: {:?}", system_data_dir).unwrap(); - - writeln!(&mut message, "[ARROW SEPARATOR]: {}", ARROW_SEPARATOR).unwrap(); - message.push_str(" Is the [ARROW_SEPARATOR] displayed correctly?\n"); - message.push_str(" If not you may want to either start zellij with a compatible mode: 'zellij options --simplified-ui true'\n"); - let mut hyperlink_compat = String::new(); - hyperlink_compat.push_str(hyperlink_start); - hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly"); - hyperlink_compat.push_str(hyperlink_mid); - hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly"); - hyperlink_compat.push_str(hyperlink_end); - write!( - &mut message, - " Or check the font that is in use:\n {}\n", - hyperlink_compat - ) - .unwrap(); - message.push_str("[MOUSE INTERACTION]: \n"); - message.push_str(" Can be temporarily disabled through pressing the [SHIFT] key.\n"); - message.push_str(" If that doesn't fix any issues consider to disable the mouse handling of zellij: 'zellij options --disable-mouse-mode'\n"); - - let default_editor = std::env::var("EDITOR") - .or_else(|_| std::env::var("VISUAL")) - .unwrap_or_else(|_| String::from("Not set, checked $EDITOR and $VISUAL")); - writeln!(&mut message, "[DEFAULT EDITOR]: {}", default_editor).unwrap(); - writeln!(&mut message, "[FEATURES]: {:?}", FEATURES).unwrap(); - let mut hyperlink = String::new(); - hyperlink.push_str(hyperlink_start); - hyperlink.push_str("https://www.zellij.dev/documentation/"); - hyperlink.push_str(hyperlink_mid); - hyperlink.push_str("zellij.dev/documentation"); - hyperlink.push_str(hyperlink_end); - writeln!(&mut message, "[DOCUMENTATION]: {}", hyperlink).unwrap(); - //printf '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n' - - std::io::stdout().write_all(message.as_bytes())?; - - Ok(()) - } - fn generate_completion(shell: &str) { - let shell: Shell = match shell.to_lowercase().parse() { - Ok(shell) => shell, - _ => { - eprintln!("Unsupported shell: {}", shell); - std::process::exit(1); - }, - }; - let mut out = std::io::stdout(); - clap_complete::generate(shell, &mut CliArgs::command(), "zellij", &mut out); - // add shell dependent extra completion - match shell { - Shell::Bash => {}, - Shell::Elvish => {}, - Shell::Fish => { - let _ = out.write_all(FISH_EXTRA_COMPLETION); - }, - Shell::PowerShell => {}, - Shell::Zsh => {}, - _ => {}, - }; - } - fn generate_auto_start(shell: &str) { - let shell: Shell = match shell.to_lowercase().parse() { - Ok(shell) => shell, - _ => { - eprintln!("Unsupported shell: {}", shell); - std::process::exit(1); - }, - }; + fn generate_auto_start(shell: &str) { + let shell: Shell = match shell.to_lowercase().parse() { + Ok(shell) => shell, + _ => { + eprintln!("Unsupported shell: {}", shell); + std::process::exit(1); + }, + }; - let mut out = std::io::stdout(); - match shell { - Shell::Bash => { - let _ = out.write_all(BASH_AUTO_START_SCRIPT); - }, - Shell::Fish => { - let _ = out.write_all(FISH_AUTO_START_SCRIPT); - }, - Shell::Zsh => { - let _ = out.write_all(ZSH_AUTO_START_SCRIPT); - }, - _ => {}, + let mut out = std::io::stdout(); + match shell { + Shell::Bash => { + let _ = out.write_all(BASH_AUTO_START_SCRIPT); + }, + Shell::Fish => { + let _ = out.write_all(FISH_AUTO_START_SCRIPT); + }, + Shell::Zsh => { + let _ = out.write_all(ZSH_AUTO_START_SCRIPT); + }, + _ => {}, + } } } -} -#[cfg(test)] -mod setup_test { - use super::Setup; - use crate::data::InputMode; - use crate::input::{ - config::{Config, ConfigError}, - layout::LayoutFromYamlIntermediate, - options::Options, - }; + #[cfg(test)] + mod setup_test { + use super::Setup; + use crate::data::InputMode; + use crate::input::{ + config::{Config, ConfigError}, + layout::LayoutFromYamlIntermediate, + options::Options, + }; - fn deserialise_config_and_layout( - config: &str, - layout: &str, - ) -> Result<(Config, LayoutFromYamlIntermediate), ConfigError> { - let config = Config::from_yaml(config)?; - let layout = LayoutFromYamlIntermediate::from_yaml(layout)?; - Ok((config, layout)) - } + fn deserialise_config_and_layout( + config: &str, + layout: &str, + ) -> Result<(Config, LayoutFromYamlIntermediate), ConfigError> { + let config = Config::from_yaml(config)?; + let layout = LayoutFromYamlIntermediate::from_yaml(layout)?; + Ok((config, layout)) + } - #[test] - fn empty_config_empty_layout() { - let goal = Config::default(); - let config = r""; - let layout = r""; - let config_layout_result = deserialise_config_and_layout(config, layout); - let (config, layout) = config_layout_result.unwrap(); - let config_options = Options::default(); - let (config, _layout, _config_options) = - Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); - assert_eq!(config, goal); - } + #[test] + fn empty_config_empty_layout() { + let goal = Config::default(); + let config = r""; + let layout = r""; + let config_layout_result = deserialise_config_and_layout(config, layout); + let (config, layout) = config_layout_result.unwrap(); + let config_options = Options::default(); + let (config, _layout, _config_options) = + Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); + assert_eq!(config, goal); + } - #[test] - fn config_empty_layout() { - let mut goal = Config::default(); - goal.options.default_shell = Some(std::path::PathBuf::from("fish")); - let config = r"--- - default_shell: fish"; - let layout = r""; - let config_layout_result = deserialise_config_and_layout(config, layout); - let (config, layout) = config_layout_result.unwrap(); - let config_options = Options::default(); - let (config, _layout, _config_options) = - Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); - assert_eq!(config, goal); - } + #[test] + fn config_empty_layout() { + let mut goal = Config::default(); + goal.options.default_shell = Some(std::path::PathBuf::from("fish")); + let config = r"--- + default_shell: fish"; + let layout = r""; + let config_layout_result = deserialise_config_and_layout(config, layout); + let (config, layout) = config_layout_result.unwrap(); + let config_options = Options::default(); + let (config, _layout, _config_options) = + Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); + assert_eq!(config, goal); + } - #[test] - fn layout_overwrites_config() { - let mut goal = Config::default(); - goal.options.default_shell = Some(std::path::PathBuf::from("bash")); - let config = r"--- - default_shell: fish"; - let layout = r"--- - default_shell: bash"; - let config_layout_result = deserialise_config_and_layout(config, layout); - let (config, layout) = config_layout_result.unwrap(); - let config_options = Options::default(); - let (config, _layout, _config_options) = - Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); - assert_eq!(config, goal); - } + #[test] + fn layout_overwrites_config() { + let mut goal = Config::default(); + goal.options.default_shell = Some(std::path::PathBuf::from("bash")); + let config = r"--- + default_shell: fish"; + let layout = r"--- + default_shell: bash"; + let config_layout_result = deserialise_config_and_layout(config, layout); + let (config, layout) = config_layout_result.unwrap(); + let config_options = Options::default(); + let (config, _layout, _config_options) = + Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); + assert_eq!(config, goal); + } - #[test] - fn empty_config_nonempty_layout() { - let mut goal = Config::default(); - goal.options.default_shell = Some(std::path::PathBuf::from("bash")); - let config = r""; - let layout = r"--- - default_shell: bash"; - let config_layout_result = deserialise_config_and_layout(config, layout); - let (config, layout) = config_layout_result.unwrap(); - let config_options = Options::default(); - let (config, _layout, _config_options) = - Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); - assert_eq!(config, goal); - } + #[test] + fn empty_config_nonempty_layout() { + let mut goal = Config::default(); + goal.options.default_shell = Some(std::path::PathBuf::from("bash")); + let config = r""; + let layout = r"--- + default_shell: bash"; + let config_layout_result = deserialise_config_and_layout(config, layout); + let (config, layout) = config_layout_result.unwrap(); + let config_options = Options::default(); + let (config, _layout, _config_options) = + Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); + assert_eq!(config, goal); + } - #[test] - fn nonempty_config_nonempty_layout() { - let mut goal = Config::default(); - goal.options.default_shell = Some(std::path::PathBuf::from("bash")); - goal.options.default_mode = Some(InputMode::Locked); - let config = r"--- - default_mode: locked"; - let layout = r"--- - default_shell: bash"; - let config_layout_result = deserialise_config_and_layout(config, layout); - let (config, layout) = config_layout_result.unwrap(); - let config_options = Options::default(); - let (config, _layout, _config_options) = - Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); - assert_eq!(config, goal); + #[test] + fn nonempty_config_nonempty_layout() { + let mut goal = Config::default(); + goal.options.default_shell = Some(std::path::PathBuf::from("bash")); + goal.options.default_mode = Some(InputMode::Locked); + let config = r"--- + default_mode: locked"; + let layout = r"--- + default_shell: bash"; + let config_layout_result = deserialise_config_and_layout(config, layout); + let (config, layout) = config_layout_result.unwrap(); + let config_options = Options::default(); + let (config, _layout, _config_options) = + Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap(); + assert_eq!(config, goal); + } } } From 484d7a562d172d2d27355c486092b5d28fb236f0 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 27 Jun 2022 12:37:42 +0200 Subject: [PATCH 05/90] utils/input/keybinds_test: Fix import of the `CharOrArrow` struct which is now part of the `data` submodule. --- zellij-utils/src/input/unit/keybinds_test.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zellij-utils/src/input/unit/keybinds_test.rs b/zellij-utils/src/input/unit/keybinds_test.rs index ed13289819..1579f0139e 100644 --- a/zellij-utils/src/input/unit/keybinds_test.rs +++ b/zellij-utils/src/input/unit/keybinds_test.rs @@ -1,7 +1,6 @@ use super::super::actions::*; use super::super::keybinds::*; -use crate::data::Key; -use crate::input::CharOrArrow; +use crate::data::{CharOrArrow, Key}; #[test] fn merge_keybinds_merges_different_keys() { From 548bb878d4d03770f2798343b16b7ec59b6ba640 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 27 Jun 2022 12:38:27 +0200 Subject: [PATCH 06/90] utils/layout: Use global serde crate Previously the code was decorated with `#[serde(crate = "self::serde")]` statements which cannot be shared with wasm. Use the regular serde without specifying which serde is meant. --- zellij-utils/src/input/layout.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index b7eff7bda2..02e1304582 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -16,7 +16,6 @@ use crate::{ pane_size::{Dimension, PaneGeom}, setup, }; -use crate::{serde, serde_yaml}; use super::{ config::ConfigFromYaml, @@ -35,7 +34,6 @@ use std::{fs::File, io::prelude::*}; use url::Url; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] -#[serde(crate = "self::serde")] pub enum Direction { #[serde(alias = "horizontal")] Horizontal, @@ -55,7 +53,6 @@ impl Not for Direction { } #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)] -#[serde(crate = "self::serde")] pub enum SplitSize { #[serde(alias = "percent")] Percent(f64), // 1 to 100 @@ -64,7 +61,6 @@ pub enum SplitSize { } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(crate = "self::serde")] pub enum Run { #[serde(rename = "plugin")] Plugin(RunPlugin), @@ -73,7 +69,6 @@ pub enum Run { } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(crate = "self::serde")] pub enum RunFromYaml { #[serde(rename = "plugin")] Plugin(RunPluginFromYaml), @@ -82,7 +77,6 @@ pub enum RunFromYaml { } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(crate = "self::serde")] pub struct RunPluginFromYaml { #[serde(default)] pub _allow_exec_host_cmd: bool, @@ -90,7 +84,6 @@ pub struct RunPluginFromYaml { } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(crate = "self::serde")] pub struct RunPlugin { #[serde(default)] pub _allow_exec_host_cmd: bool, @@ -98,7 +91,6 @@ pub struct RunPlugin { } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(crate = "self::serde")] pub enum RunPluginLocation { File(PathBuf), Zellij(PluginTag), @@ -133,7 +125,6 @@ impl fmt::Display for RunPluginLocation { // The layout struct ultimately used to build the layouts. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(crate = "self::serde")] pub struct Layout { pub direction: Direction, #[serde(default)] @@ -152,7 +143,6 @@ pub struct Layout { // https://github.com/bincode-org/bincode/issues/245 // flattened fields don't retain size information. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(crate = "self::serde")] #[serde(default)] pub struct LayoutFromYamlIntermediate { #[serde(default)] @@ -170,7 +160,6 @@ pub struct LayoutFromYamlIntermediate { // The struct that is used to deserialize the layout from // a yaml configuration file #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] -#[serde(crate = "self::serde")] #[serde(default)] pub struct LayoutFromYaml { #[serde(default)] @@ -422,7 +411,6 @@ impl LayoutFromYaml { // The struct that is used to deserialize the session from // a yaml configuration file #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] -#[serde(crate = "self::serde")] pub struct SessionFromYaml { pub name: Option, #[serde(default = "default_as_some_true")] @@ -436,7 +424,6 @@ fn default_as_some_true() -> Option { // The struct that carries the information template that is used to // construct the layout #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(crate = "self::serde")] pub struct LayoutTemplate { pub direction: Direction, #[serde(default)] @@ -482,7 +469,6 @@ impl LayoutTemplate { // The tab-layout struct used to specify each individual tab. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(crate = "self::serde")] pub struct TabLayout { #[serde(default)] pub direction: Direction, From 1264cb30a9fe381d48e1acf2759d1d051fd330ee Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 27 Jun 2022 12:39:46 +0200 Subject: [PATCH 07/90] utils/data: Implement `fmt::Display` for `Key` so the Keybindings can be displayed via `format!` and friends in e.g. the status bar. --- zellij-utils/src/data.rs | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 82ed5da9e3..58c0a99a55 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -52,6 +52,31 @@ pub enum Key { Esc, } +impl fmt::Display for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Key::Backspace => write!(f, "Backspace"), + Key::Left => write!(f, "{}", Direction::Left), + Key::Right => write!(f, "{}", Direction::Right), + Key::Up => write!(f, "{}", Direction::Up), + Key::Down => write!(f, "{}", Direction::Down), + Key::Home => write!(f, "Home"), + Key::End => write!(f, "End"), + Key::PageUp => write!(f, "PgUp"), + Key::PageDown => write!(f, "PgDown"), + Key::BackTab => write!(f, "BackTab"), + Key::Delete => write!(f, "Del"), + Key::Insert => write!(f, "Ins"), + Key::F(n) => write!(f, "F{}", n), + Key::Char(c) => write!(f, "{}", c), + Key::Alt(c) => write!(f, "{}", c), + Key::Ctrl(c) => write!(f, "Ctrl+{}", c), + Key::Null => write!(f, "NULL"), + Key::Esc => write!(f, "Esc"), + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(untagged)] pub enum CharOrArrow { @@ -59,6 +84,15 @@ pub enum CharOrArrow { Direction(Direction), } +impl fmt::Display for CharOrArrow { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CharOrArrow::Char(c) => write!(f, "{}", c), + CharOrArrow::Direction(d) => write!(f, "{}", d), + } + } +} + /// The four directions (left, right, up, down). #[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize)] pub enum Direction { @@ -68,6 +102,17 @@ pub enum Direction { Down, } +impl fmt::Display for Direction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Direction::Left => write!(f, "←"), + Direction::Right => write!(f, "→"), + Direction::Up => write!(f, "↑"), + Direction::Down => write!(f, "↓"), + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] // FIXME: This should be extended to handle different button clicks (not just // left click) and the `ScrollUp` and `ScrollDown` events could probably be From 923b33b649ad06c9bce9dd90aace4928fc48da84 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 27 Jun 2022 12:40:27 +0200 Subject: [PATCH 08/90] tile/prelude: Re-export `actions` submodule of `zellij_utils` so the plugins can access the `ModeKeybinds` struct with all of its members. --- zellij-tile/src/prelude.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zellij-tile/src/prelude.rs b/zellij-tile/src/prelude.rs index 196696a4c9..8190206673 100644 --- a/zellij-tile/src/prelude.rs +++ b/zellij-tile/src/prelude.rs @@ -1,3 +1,4 @@ pub use crate::shim::*; pub use crate::*; pub use zellij_utils::data::*; +pub use zellij_utils::input::actions; From 6bda877b409ab6bd5c2730723cc20600c39344b2 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 09:40:36 +0200 Subject: [PATCH 09/90] utils/data: Fix `ModeInfo::keybinds` type and transfer a vector of `(Key, Vec)` to the plugins so they can parse it themselves, instead of passing strings around. Due to the requirement of the `Eq` trait derive on `ModeInfo` this requires deriving `Eq` on all the types included by `Key` and `Action` as well. Note that `Action` includes the `layout::SplitSize` structure as a member. We cannot derive `Eq` here since `SplitSize::Percent(f64)` cannot satisfy `Eq` because `f64` doesn't implement this. So we add a new type to hack around this limitation by storing the percentage as `u64` internally, scaled by a factor of 10 000 and transforming it to f64 when needed. Refer to the documentation of `layout::Percent` for further information. --- zellij-utils/src/data.rs | 6 +++--- zellij-utils/src/input/actions.rs | 4 ++-- zellij-utils/src/input/layout.rs | 32 ++++++++++++++++++++++++++----- zellij-utils/src/position.rs | 6 +++--- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 58c0a99a55..2defdc3617 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1,3 +1,4 @@ +use crate::input::actions::Action; use clap::ArgEnum; use serde::{Deserialize, Serialize}; use std::fmt; @@ -285,11 +286,10 @@ pub struct Style { /// Represents the contents of the help message that is printed in the status bar, /// which indicates the current [`InputMode`] and what the keybinds for that mode /// are. Related to the default `status-bar` plugin. -#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ModeInfo { pub mode: InputMode, - // FIXME: This should probably return Keys and Actions, then sort out strings plugin-side - pub keybinds: Vec<(String, String)>, // => + pub keybinds: Vec<(Key, Vec)>, pub style: Style, pub capabilities: PluginCapabilities, pub session_name: Option, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 17778c4e95..aaf0264e75 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -17,7 +17,7 @@ pub enum Direction { Down, } -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum ResizeDirection { Left, Right, @@ -45,7 +45,7 @@ pub enum SearchOption { // They might need to be adjusted in the default config // as well `../../assets/config/default.yaml` /// Actions that can be bound to keys. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum Action { /// Quit Zellij. Quit, diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 02e1304582..70de3b15fb 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -52,14 +52,36 @@ impl Not for Direction { } } -#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum SplitSize { #[serde(alias = "percent")] - Percent(f64), // 1 to 100 + Percent(Percent), // 1 to 100 #[serde(alias = "fixed")] Fixed(usize), // An absolute number of columns or rows } +// FIXME: This is an ugly hack around the fact that f64 as type (which was previously what +// `SplitSize::Percent(T)` used as inner type `T`) doesn't implement `Eq` and `Hash` traits. So we +// make a custom type that encloses a u64 instead and add conversion methods to/from f64. Of course +// that breaks the interface and all of the tests. This type is, through a long chain of structs, +// part of the `zellij_utils::data::ModeInfo` struct that derives the `Eq` trait on itself, hence +// this is needed here. +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +pub struct Percent(pub u64); + +impl From for Percent { + fn from(val: f64) -> Percent { + Percent((val * 10000.0) as u64) + } +} + +impl From for f64 { + fn from(val: Percent) -> f64 { + let Percent(val) = val; + (val as f64) / 10000.0 + } +} + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub enum Run { #[serde(rename = "plugin")] @@ -468,7 +490,7 @@ impl LayoutTemplate { } // The tab-layout struct used to specify each individual tab. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct TabLayout { #[serde(default)] pub direction: Direction, @@ -592,7 +614,7 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG for (&size, part) in sizes.iter().zip(&layout.parts) { let split_dimension = match size { - Some(SplitSize::Percent(percent)) => Dimension::percent(percent), + Some(SplitSize::Percent(percent)) => Dimension::percent(percent.into()), Some(SplitSize::Fixed(size)) => Dimension::fixed(size), None => { let free_percent = if let Some(p) = split_dimension_space.as_percent() { @@ -600,7 +622,7 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG .iter() .map(|&s| { if let Some(SplitSize::Percent(ip)) = s { - ip + ip.into() } else { 0.0 } diff --git a/zellij-utils/src/position.rs b/zellij-utils/src/position.rs index 89b91f8225..783421c574 100644 --- a/zellij-utils/src/position.rs +++ b/zellij-utils/src/position.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Deserialize, Serialize)] +#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq, PartialOrd, Deserialize, Serialize)] pub struct Position { pub line: Line, pub column: Column, @@ -30,7 +30,7 @@ impl Position { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, PartialOrd)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd)] pub struct Line(pub isize); -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, PartialOrd)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd)] pub struct Column(pub usize); From e6e6bf4e29dd2eb647d9c3e6a8c18a0e74ede904 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 12:15:58 +0200 Subject: [PATCH 10/90] utils/data: Make `Key` sortable so the keybindings can be sorted after their keys. --- zellij-utils/src/data.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 2defdc3617..8824b0c783 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -31,7 +31,7 @@ pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) { (colors.green, colors.black) } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] pub enum Key { Backspace, Left, @@ -78,7 +78,7 @@ impl fmt::Display for Key { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] #[serde(untagged)] pub enum CharOrArrow { Char(char), @@ -95,7 +95,7 @@ impl fmt::Display for CharOrArrow { } /// The four directions (left, right, up, down). -#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize)] +#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] pub enum Direction { Left, Right, From ff744170876b69220c21214116545800e94586a1 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 12:16:35 +0200 Subject: [PATCH 11/90] WIP: utils/input: Make keybinds accessible when generating `ModeInfo` structs. --- zellij-utils/src/input/actions.rs | 4 +- zellij-utils/src/input/keybinds.rs | 13 ++++++ zellij-utils/src/input/mod.rs | 72 ++---------------------------- 3 files changed, 19 insertions(+), 70 deletions(-) diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index aaf0264e75..a20d490dd5 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -27,13 +27,13 @@ pub enum ResizeDirection { Decrease, } -#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum SearchDirection { Down, Up, } -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum SearchOption { CaseSensitivity, WholeWord, diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index 9f948f898c..1839a60b14 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -84,6 +84,12 @@ impl Keybinds { .keybinds } + pub fn get_mode_keybinds(&self, mode: &InputMode) -> &ModeKeybinds { + self.0 + .get(mode) + .expect("Failed to get Keybinds for current mode") + } + /// Entrypoint from the config module pub fn get_default_keybinds_with_config(from_yaml: Option) -> Keybinds { let default_keybinds = match from_yaml.clone() { @@ -239,6 +245,13 @@ impl ModeKeybinds { } keymap } + + pub fn to_cloned_vec(&self) -> Vec<(Key, Vec)> { + self.0 + .iter() + .map(|(key, vac)| (*key, vac.clone())) + .collect() + } } impl From for Keybinds { diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 8473a0eeef..1c34e57256 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -13,80 +13,16 @@ pub mod theme; pub mod mouse; use crate::{ - envs, data::{InputMode, Key, ModeInfo, PluginCapabilities, Style}, + envs, + input::keybinds::Keybinds, }; /// Creates a [`ModeInfo`] struct indicating the current [`InputMode`] and its keybinds /// (as pairs of [`String`]s). pub fn get_mode_info(mode: InputMode, style: Style, capabilities: PluginCapabilities) -> ModeInfo { - let keybinds = match mode { - InputMode::Normal | InputMode::Locked | InputMode::Prompt => Vec::new(), - InputMode::Resize => vec![ - ("←↓↑→".to_string(), "Resize".to_string()), - ("+-".to_string(), "Increase/Decrease size".to_string()), - ], - InputMode::Move => vec![ - ("←↓↑→".to_string(), "Move".to_string()), - ("n/Tab".to_string(), "Next Pane".to_string()), - ], - InputMode::Pane => vec![ - ("←↓↑→".to_string(), "Move focus".to_string()), - ("n".to_string(), "New".to_string()), - ("d".to_string(), "Down split".to_string()), - ("r".to_string(), "Right split".to_string()), - ("x".to_string(), "Close".to_string()), - ("f".to_string(), "Fullscreen".to_string()), - ("z".to_string(), "Frames".to_string()), - ("c".to_string(), "Rename".to_string()), - ("w".to_string(), "Floating Toggle".to_string()), - ("e".to_string(), "Embed Pane".to_string()), - ("p".to_string(), "Next".to_string()), - ], - InputMode::Tab => vec![ - ("←↓↑→".to_string(), "Move focus".to_string()), - ("n".to_string(), "New".to_string()), - ("x".to_string(), "Close".to_string()), - ("r".to_string(), "Rename".to_string()), - ("s".to_string(), "Sync".to_string()), - ("Tab".to_string(), "Toggle".to_string()), - ], - InputMode::Scroll => vec![ - ("↓↑".to_string(), "Scroll".to_string()), - ("PgDn/PgUp".to_string(), "Scroll Page".to_string()), - ("d/u".to_string(), "Scroll Half Page".to_string()), - ( - "e".to_string(), - "Edit Scrollback in Default Editor".to_string(), - ), - ("s".to_string(), "Enter search term".to_string()), - ], - InputMode::EnterSearch => vec![("Enter".to_string(), "when done".to_string())], - InputMode::Search => vec![ - ("↓↑".to_string(), "Scroll".to_string()), - ("PgUp/PgDn".to_string(), "Scroll Page".to_string()), - ("u/d".to_string(), "Scroll Half Page".to_string()), - ("n".to_string(), "Search down".to_string()), - ("p".to_string(), "Search up".to_string()), - ("c".to_string(), "Case sensitivity".to_string()), - ("w".to_string(), "Wrap".to_string()), - ("o".to_string(), "Whole words".to_string()), - ], - InputMode::RenameTab => vec![("Enter".to_string(), "when done".to_string())], - InputMode::RenamePane => vec![("Enter".to_string(), "when done".to_string())], - InputMode::Session => vec![("d".to_string(), "Detach".to_string())], - InputMode::Tmux => vec![ - ("←↓↑→".to_string(), "Move focus".to_string()), - ("\"".to_string(), "Split Down".to_string()), - ("%".to_string(), "Split Right".to_string()), - ("z".to_string(), "Fullscreen".to_string()), - ("c".to_string(), "New Tab".to_string()), - (",".to_string(), "Rename Tab".to_string()), - ("p".to_string(), "Previous Tab".to_string()), - ("n".to_string(), "Next Tab".to_string()), - ], - }; - + // FIXME: Need access to the real keybindings here + let keybinds = Keybinds::default().get_mode_keybinds(&mode).to_cloned_vec(); let session_name = envs::get_session_name().ok(); ModeInfo { From 7adffd9c41614e12565c3030fa850d5e27dc275c Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 15:41:50 +0200 Subject: [PATCH 12/90] utils/data: Handle unprintable chars in `Key` when displaying via the `fmt::Display` trait. Handles `\t` and `\n` and represents them as UTF-8 arrow glyphs. --- zellij-utils/src/data.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 8824b0c783..63a4542c65 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -69,7 +69,11 @@ impl fmt::Display for Key { Key::Delete => write!(f, "Del"), Key::Insert => write!(f, "Ins"), Key::F(n) => write!(f, "F{}", n), - Key::Char(c) => write!(f, "{}", c), + Key::Char(c) => match c { + '\n' => write!(f, "↩"), + '\t' => write!(f, "↹"), + _ => write!(f, "{}", c), + }, Key::Alt(c) => write!(f, "{}", c), Key::Ctrl(c) => write!(f, "Ctrl+{}", c), Key::Null => write!(f, "NULL"), From 78184d71f13351d3da902c362c6744b3eef1227d Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 15:43:35 +0200 Subject: [PATCH 13/90] HACK: utils/layout: Use u64 for SplitSize::Percent The previous workaround using a custom `Percent` type fails at the absolute latest when confronted with user layouts, since these do not know about the scaling factor and will thus break. It still breaks currently because `Percent` now expects a u64 (i.e. `50`, not `50.0`) but this is more easily explained and understood. --- zellij-utils/src/input/layout.rs | 36 +++++++++---------- zellij-utils/src/input/unit/layout_test.rs | 42 +++++++++++----------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 70de3b15fb..aba13e64f3 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -55,7 +55,7 @@ impl Not for Direction { #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum SplitSize { #[serde(alias = "percent")] - Percent(Percent), // 1 to 100 + Percent(u64), // 1 to 100 #[serde(alias = "fixed")] Fixed(usize), // An absolute number of columns or rows } @@ -66,21 +66,21 @@ pub enum SplitSize { // that breaks the interface and all of the tests. This type is, through a long chain of structs, // part of the `zellij_utils::data::ModeInfo` struct that derives the `Eq` trait on itself, hence // this is needed here. -#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] -pub struct Percent(pub u64); - -impl From for Percent { - fn from(val: f64) -> Percent { - Percent((val * 10000.0) as u64) - } -} - -impl From for f64 { - fn from(val: Percent) -> f64 { - let Percent(val) = val; - (val as f64) / 10000.0 - } -} +//#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +//pub struct Percent(pub u64); +// +//impl From for Percent { +// fn from(val: f64) -> Percent { +// Percent((val * 10000.0) as u64) +// } +//} +// +//impl From for f64 { +// fn from(val: Percent) -> f64 { +// let Percent(val) = val; +// (val as f64) / 10000.0 +// } +//} #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub enum Run { @@ -614,7 +614,7 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG for (&size, part) in sizes.iter().zip(&layout.parts) { let split_dimension = match size { - Some(SplitSize::Percent(percent)) => Dimension::percent(percent.into()), + Some(SplitSize::Percent(percent)) => Dimension::percent(percent as f64), Some(SplitSize::Fixed(size)) => Dimension::fixed(size), None => { let free_percent = if let Some(p) = split_dimension_space.as_percent() { @@ -622,7 +622,7 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG .iter() .map(|&s| { if let Some(SplitSize::Percent(ip)) = s { - ip.into() + ip as f64 } else { 0.0 } diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 292b6b01dc..22a10c6fb1 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -198,7 +198,7 @@ fn three_panes_with_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -213,7 +213,7 @@ fn three_panes_with_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -222,7 +222,7 @@ fn three_panes_with_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, ], @@ -319,7 +319,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -334,7 +334,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -343,7 +343,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, ], @@ -467,7 +467,7 @@ fn deeply_nested_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(21.0)), + split_size: Some(SplitSize::Percent(21)), run: None, }, Layout { @@ -482,7 +482,7 @@ fn deeply_nested_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(22.0)), + split_size: Some(SplitSize::Percent(22)), run: None, }, Layout { @@ -497,7 +497,7 @@ fn deeply_nested_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(23.0)), + split_size: Some(SplitSize::Percent(23)), run: None, }, Layout { @@ -506,19 +506,19 @@ fn deeply_nested_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(24.0)), + split_size: Some(SplitSize::Percent(24)), run: None, }, ], - split_size: Some(SplitSize::Percent(78.0)), + split_size: Some(SplitSize::Percent(78)), run: None, }, ], - split_size: Some(SplitSize::Percent(79.0)), + split_size: Some(SplitSize::Percent(79)), run: None, }, ], - split_size: Some(SplitSize::Percent(90.0)), + split_size: Some(SplitSize::Percent(90)), run: None, }, Layout { @@ -527,7 +527,7 @@ fn deeply_nested_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(15.0)), + split_size: Some(SplitSize::Percent(15)), run: None, }, Layout { @@ -536,7 +536,7 @@ fn deeply_nested_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(15.0)), + split_size: Some(SplitSize::Percent(15)), run: None, }, Layout { @@ -545,7 +545,7 @@ fn deeply_nested_tab_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(15.0)), + split_size: Some(SplitSize::Percent(15)), run: None, }, ], @@ -591,7 +591,7 @@ fn three_tabs_tab_one_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -638,7 +638,7 @@ fn three_tabs_tab_two_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -651,7 +651,7 @@ fn three_tabs_tab_two_merged_correctly() { run: None, }, ], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -698,7 +698,7 @@ fn three_tabs_tab_three_merged_correctly() { pane_name: None, focus: None, parts: vec![], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { @@ -711,7 +711,7 @@ fn three_tabs_tab_three_merged_correctly() { run: None, }, ], - split_size: Some(SplitSize::Percent(50.0)), + split_size: Some(SplitSize::Percent(50)), run: None, }, Layout { From 0ff8b4fa618adb04859bddbd70a64ecb4e8c79ba Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 15:45:50 +0200 Subject: [PATCH 14/90] status-bar: Add helper macros that retrieve the key bound to execute a sequence of `Action` given a specific Keybinding, and a shorthand that expands to `Action::SwitchToMode(InputMode::Normal)` used for pattern matching with the `matches!` macro. --- default-plugins/status-bar/src/main.rs | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 23ee3de769..99e4b4399b 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -330,3 +330,39 @@ impl State { } } } + +/// Get key from action pattern(s). +/// +/// This macro takes as arguments a `keymap` that is a `Vec<(Key, Vec)>` and contains all +/// keybindings for the current mode and one or more `p` patterns which match a sequence of actions +/// to search for. If within the keymap a sequence of actions matching `p` is found, all keys that +/// trigger the action pattern are returned as vector of `Vec`. +// TODO: Accept multiple sequences of patterns, possible separated by '|', and bin them together +// into one group under 'text'. +#[macro_export] +macro_rules! action_key { + ($keymap:ident, $( $p:pat ),+) => { + //let mut ret: Vec; + $keymap.iter(). + filter_map(|(key, acvec)| { + match matches!(acvec.as_slice(), &[$($p),+]) { + true => Some(*key), + false => None + } + }) + .collect::>() + }; +} + +/// Helper macro to represent common pattern. +/// +/// Expands verbosely to `Action::SwitchToMode(InputMode::Normal)`, which is an action that often +/// repeats in the keybindings configuration. We need it to expand to verbose rust code (i.e. a +/// "Textual replacement", similar to C `#define`) so it gets picked up as proper pattern in the +/// `key_hint!` macro. +#[macro_export] +macro_rules! to_normal { + () => { + Action::SwitchToMode(InputMode::Normal) + }; +} From 40e79d5a2d8b73c96f003dc2bdaccd876c21e1a7 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 15:50:41 +0200 Subject: [PATCH 15/90] status-bar/first_line: Get shared superkey if any from the `ModeKeybindings` in the current `ModeInfo` struct. If the configured keybindings for switching the modes don't have a superkey in common, do not print a common prefix. --- default-plugins/status-bar/src/first_line.rs | 44 +++++++++++++++++--- default-plugins/status-bar/src/main.rs | 2 +- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 6af5014415..898e4fbfa0 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -306,12 +306,44 @@ fn key_indicators( line_part } -pub fn superkey(palette: ColoredElements, separator: &str) -> LinePart { - let prefix_text = if separator.is_empty() { - " Ctrl + " - } else { - " Ctrl +" - }; +/// Return a Vector of tuples (Key, InputMode) where each "Key" is a shortcut to switch to +/// "InputMode". +pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<(&Key, &InputMode)> { + mode_info + .keybinds + .iter() + .filter_map(|(key, vac)| match vac.first() { + None => None, + Some(vac) => { + if let actions::Action::SwitchToMode(mode) = vac { + return Some((key, mode)); + } + None + }, + }) + .collect() +} + +pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) -> LinePart { + // Find a common modifier if any + let mut prefix_text: &str = ""; + let mut new_prefix; + for (key, _mode) in mode_switch_keys(mode_info).iter() { + match key { + Key::F(_) => new_prefix = " F", + Key::Ctrl(_) => new_prefix = " Ctrl +", + Key::Alt(_) => new_prefix = " Alt +", + _ => break, + } + if prefix_text.is_empty() { + prefix_text = new_prefix; + } else if prefix_text != new_prefix { + // Prefix changed! + prefix_text = ""; + break; + } + } + let prefix = palette.superkey_prefix.paint(prefix_text); let suffix_separator = palette.superkey_suffix_separator.paint(separator); LinePart { diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 99e4b4399b..8d2ec04e20 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -264,7 +264,7 @@ impl ZellijPlugin for State { }; let colored_elements = color_elements(self.mode_info.style.colors, !supports_arrow_fonts); - let superkey = superkey(colored_elements, separator); + let superkey = superkey(colored_elements, separator, &self.mode_info); let ctrl_keys = ctrl_keys( &self.mode_info, cols.saturating_sub(superkey.len), From 5ba84fb6a40c67945df5ffd51a4d7bd0d3b90243 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 15:52:48 +0200 Subject: [PATCH 16/90] status-bar/first_line: Add key to KeyShortcut which is the key that must be pressed in the current mode to execute the given shortcut (i.e. switch to the given mode). --- default-plugins/status-bar/src/first_line.rs | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 898e4fbfa0..6a8f5ce33e 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -7,6 +7,7 @@ use crate::{ColoredElements, LinePart}; struct KeyShortcut { mode: KeyMode, action: KeyAction, + key: Key, } enum KeyAction { @@ -29,8 +30,8 @@ enum KeyMode { impl KeyShortcut { //pub fn new(mode: KeyMode, action: KeyAction, bind: KeyBind) -> Self { - pub fn new(mode: KeyMode, action: KeyAction) -> Self { - KeyShortcut { mode, action } //, bind } + pub fn new(mode: KeyMode, action: KeyAction, key: Key) -> Self { + KeyShortcut { mode, action, key } //, bind } } pub fn full_text(&self) -> String { @@ -45,16 +46,17 @@ impl KeyShortcut { KeyAction::Move => String::from("MOVE"), } } - pub fn letter_shortcut(&self) -> char { - match self.action { - KeyAction::Lock => 'g', - KeyAction::Pane => 'p', - KeyAction::Tab => 't', - KeyAction::Resize => 'n', - KeyAction::Search => 's', - KeyAction::Quit => 'q', - KeyAction::Session => 'o', - KeyAction::Move => 'h', + pub fn letter_shortcut(&self, with_prefix: bool) -> String { + if with_prefix { + format!("{}", self.key) + } else { + match self.key { + Key::F(c) => format!("{}", c), + Key::Ctrl(c) => format!("{}", c), + Key::Char(_) => format!("{}", self.key), + Key::Alt(c) => format!("{}", c), + _ => String::from("??"), + } } } } From ca163cb6430b09332776baa65219d6d99dac2552 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 15:54:44 +0200 Subject: [PATCH 17/90] status-bar/first_line: Dynamically set mode binds Read the keybindings for switching the modes to print in the first line from the actually configured keybindings for the current mode. Add some logic to the code that: - Prints only the "single letter" of the keybinding if all mode-switch shortcuts *share the same modifier key*, - Or prints the whole keybinding (with modified) into each segment if there is no common modifier key. --- default-plugins/status-bar/src/first_line.rs | 130 ++++++++++++++----- default-plugins/status-bar/src/main.rs | 1 + 2 files changed, 97 insertions(+), 34 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 6a8f5ce33e..1c3102cdd4 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -1,7 +1,9 @@ use ansi_term::ANSIStrings; +use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; use crate::color_elements; +use crate::{action_key, to_normal}; use crate::{ColoredElements, LinePart}; struct KeyShortcut { @@ -62,7 +64,7 @@ impl KeyShortcut { } fn unselected_mode_shortcut( - letter: char, + letter: &str, text: &str, palette: ColoredElements, separator: &str, @@ -83,12 +85,12 @@ fn unselected_mode_shortcut( suffix_separator, ]) .to_string(), - len: text.chars().count() + 7, // 2 for the arrows, 3 for the char separators, 1 for the character, 1 for the text padding + len: text.chars().count() + 6 + letter.len(), // 2 for the arrows, 3 for the char separators, 1 for the text padding } } fn unselected_alternate_mode_shortcut( - letter: char, + letter: &str, text: &str, palette: ColoredElements, separator: &str, @@ -117,12 +119,12 @@ fn unselected_alternate_mode_shortcut( suffix_separator, ]) .to_string(), - len: text.chars().count() + 7, // 2 for the arrows, 3 for the char separators, 1 for the character, 1 for the text padding + len: text.chars().count() + 6 + letter.len(), // 2 for the arrows, 3 for the char separators, 1 for the text padding } } fn selected_mode_shortcut( - letter: char, + letter: &str, text: &str, palette: ColoredElements, separator: &str, @@ -143,7 +145,7 @@ fn selected_mode_shortcut( suffix_separator, ]) .to_string(), - len: text.chars().count() + 7, // 2 for the arrows, 3 for the char separators, 1 for the character, 1 for the text padding + len: text.chars().count() + 6 + letter.len(), // 2 for the arrows, 3 for the char separators, 1 for the text padding } } @@ -223,24 +225,29 @@ fn unselected_alternate_mode_shortcut_single_letter( } } -fn full_ctrl_key(key: &KeyShortcut, palette: ColoredElements, separator: &str) -> LinePart { +fn full_ctrl_key( + key: &KeyShortcut, + palette: ColoredElements, + separator: &str, + shared_super: bool, +) -> LinePart { let full_text = key.full_text(); - let letter_shortcut = key.letter_shortcut(); + let letter_shortcut = key.letter_shortcut(!shared_super); match key.mode { KeyMode::Unselected => unselected_mode_shortcut( - letter_shortcut, + &letter_shortcut, &format!(" {}", full_text), palette, separator, ), KeyMode::UnselectedAlternate => unselected_alternate_mode_shortcut( - letter_shortcut, + &letter_shortcut, &format!(" {}", full_text), palette, separator, ), KeyMode::Selected => selected_mode_shortcut( - letter_shortcut, + &letter_shortcut, &format!(" {}", full_text), palette, separator, @@ -258,7 +265,7 @@ fn single_letter_ctrl_key( palette: ColoredElements, separator: &str, ) -> LinePart { - let letter_shortcut = key.letter_shortcut(); + let letter_shortcut = key.letter_shortcut(false).chars().next().unwrap(); match key.mode { KeyMode::Unselected => { unselected_mode_shortcut_single_letter(letter_shortcut, palette, separator) @@ -280,11 +287,12 @@ fn key_indicators( keys: &[KeyShortcut], palette: ColoredElements, separator: &str, + shared_super: bool, ) -> LinePart { // Print full-width hints let mut line_part = LinePart::default(); for ctrl_key in keys { - let key = full_ctrl_key(ctrl_key, palette, separator); + let key = full_ctrl_key(ctrl_key, palette, separator, shared_super); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -354,38 +362,92 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) } } -pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { +pub fn to_char(kv: Vec) -> Key { + kv.into_iter() + .filter(|key| { + // These are general "keybindings" to get back to normal, they aren't interesting here. + // The user will figure these out for himself if he configured no other. + matches!(key, Key::Char('\n') | Key::Char(' ') | Key::Esc) + }) + .collect::>() + .into_iter() + .next() + .unwrap_or(Key::Char('?')) +} + +pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str, shared_super: bool) -> LinePart { let supports_arrow_fonts = !help.capabilities.arrow_fonts; let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); + let binds = &help.keybinds; // Unselect all by default let mut default_keys = [ - KeyShortcut::new(KeyMode::Unselected, KeyAction::Lock), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Pane), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Tab), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Resize), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Move), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Search), - KeyShortcut::new(KeyMode::Unselected, KeyAction::Session), - KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Quit), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Lock, + to_char(action_key!(binds, Action::SwitchToMode(InputMode::Locked))), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Pane, + to_char(action_key!(binds, Action::SwitchToMode(InputMode::Pane))), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Tab, + to_char(action_key!(binds, Action::SwitchToMode(InputMode::Tab))), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Resize, + to_char(action_key!(binds, Action::SwitchToMode(InputMode::Resize))), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Move, + to_char(action_key!(binds, Action::SwitchToMode(InputMode::Move))), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Search, + to_char(action_key!(binds, Action::SwitchToMode(InputMode::Scroll))), + ), + KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Session, + to_char(action_key!(binds, Action::SwitchToMode(InputMode::Session))), + ), + KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Quit, + to_char(action_key!(binds, Action::Quit)), + ), ]; - match &help.mode { - InputMode::Normal | InputMode::Prompt | InputMode::Tmux => (), + let mode_index = match &help.mode { + InputMode::Normal | InputMode::Prompt | InputMode::Tmux => None, InputMode::Locked => { - default_keys[0].mode = KeyMode::Selected; for key in default_keys.iter_mut().skip(1) { key.mode = KeyMode::Disabled; } + Some(0) }, - InputMode::Pane | InputMode::RenamePane => default_keys[1].mode = KeyMode::Selected, - InputMode::Tab | InputMode::RenameTab => default_keys[2].mode = KeyMode::Selected, - InputMode::Resize => default_keys[3].mode = KeyMode::Selected, - InputMode::Move => default_keys[4].mode = KeyMode::Selected, - InputMode::Scroll | InputMode::Search | InputMode::EnterSearch => { - default_keys[5].mode = KeyMode::Selected - }, - InputMode::Session => default_keys[6].mode = KeyMode::Selected, + InputMode::Pane | InputMode::RenamePane => Some(1), + InputMode::Tab | InputMode::RenameTab => Some(2), + InputMode::Resize => Some(3), + InputMode::Move => Some(4), + InputMode::Scroll | InputMode::Search | InputMode::EnterSearch => Some(5), + InputMode::Session => Some(6), + }; + if let Some(index) = mode_index { + default_keys[index].mode = KeyMode::Selected; + default_keys[index].key = to_char(action_key!(binds, to_normal!())); } - key_indicators(max_len, &default_keys, colored_elements, separator) + key_indicators( + max_len, + &default_keys, + colored_elements, + separator, + shared_super, + ) } diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 8d2ec04e20..1348f41725 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -269,6 +269,7 @@ impl ZellijPlugin for State { &self.mode_info, cols.saturating_sub(superkey.len), separator, + superkey.len > 0, ); let first_line = format!("{}{}", superkey, ctrl_keys); From a770f80d5a7f855726cc2912177b3e79fbf80f83 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 15:58:18 +0200 Subject: [PATCH 18/90] status-bar/second_line: Display configured binds Instead of showing some hard-coded default values. For each mode, reads the keybindings from the configured keybindings based on some sequence of action. For example, the keybinding for `New` in the `Pane` menu is now determined by looking into the configured keybindings and finding what key is bound to the `Action::NewPane(None)` action. If no keybinding is found for a given sequence of actions, it will not show up in the segments either. --- default-plugins/status-bar/src/second_line.rs | 212 +++++++++++++++--- 1 file changed, 187 insertions(+), 25 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 40be3fcd5f..6f92489d86 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -1,8 +1,11 @@ +use super::{action_key, to_normal}; use ansi_term::{ ANSIStrings, Color::{Fixed, RGB}, Style, }; +use std::collections::VecDeque; +use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; use zellij_tile_utils::palette_match; @@ -26,31 +29,42 @@ enum StatusBarTextBoldness { fn full_length_shortcut( is_first_shortcut: bool, - letter: &str, - description: &str, + key: Vec, + action: &str, palette: Palette, ) -> LinePart { let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, ThemeHue::Light => palette.black, }); + let key = key + .iter() + .map(|key| format!("{}", key)) + .collect::>() + .join(""); + if key.is_empty() { + return LinePart { + part: "".to_string(), + len: 0, + }; + } let green_color = palette_match!(palette.green); let separator = if is_first_shortcut { " " } else { " / " }; let separator = Style::new().fg(text_color).paint(separator); - let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space + let shortcut_len = key.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space let shortcut_left_separator = Style::new().fg(text_color).paint("<"); - let shortcut = Style::new().fg(green_color).bold().paint(letter); + let shortcut = Style::new().fg(green_color).bold().paint(key); let shortcut_right_separator = Style::new().fg(text_color).paint("> "); - let description_len = description.chars().count(); - let description = Style::new().fg(text_color).bold().paint(description); - let len = shortcut_len + description_len + separator.chars().count(); + let action_len = action.chars().count(); + let action = Style::new().fg(text_color).bold().paint(action); + let len = shortcut_len + action_len + separator.chars().count(); LinePart { part: ANSIStrings(&[ separator, shortcut_left_separator, shortcut, shortcut_right_separator, - description, + action, ]) .to_string(), len, @@ -59,14 +73,16 @@ fn full_length_shortcut( fn first_word_shortcut( is_first_shortcut: bool, - letter: &str, - description: &str, + key: &Key, + _action: &[Action], palette: Palette, ) -> LinePart { let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, ThemeHue::Light => palette.black, }); + let letter = format!("{}", key); + let description = "test".to_string(); let green_color = palette_match!(palette.green); let separator = if is_first_shortcut { " " } else { " / " }; let separator = Style::new().fg(text_color).paint(separator); @@ -173,20 +189,172 @@ fn select_pane_shortcut(palette: Palette) -> LinePart { show_extra_hints(palette, text_with_style.to_vec()) } +fn add_shortcut(help: &ModeInfo, mut linepart: LinePart, text: &str, keys: Vec) -> LinePart { + let shortcut = full_length_shortcut(false, keys, text, help.style.colors); + linepart.len += shortcut.len; + linepart.part = format!("{}{}", linepart.part, shortcut); + linepart +} + fn full_shortcut_list_nonstandard_mode( extra_hint_producing_function: fn(Palette) -> LinePart, ) -> impl FnOnce(&ModeInfo) -> LinePart { move |help| { - let mut line_part = LinePart::default(); - for (i, (letter, description)) in help.keybinds.iter().enumerate() { - let shortcut = full_length_shortcut(i == 0, letter, description, help.style.colors); - line_part.len += shortcut.len; - line_part.part = format!("{}{}", line_part.part, shortcut,); + let km: VecDeque<(Key, Vec)> = help.keybinds.clone().into(); + let mut lp = LinePart::default(); + + if help.mode == InputMode::Pane || help.mode == InputMode::Tab { + // Shared keybindings + lp = add_shortcut( + help, + lp, + "Move Focus", + action_key!(km, Action::MoveFocus(_)), + ); + lp = add_shortcut(help, lp, "New", action_key!(km, Action::NewPane(None))); + lp = add_shortcut( + help, + lp, + "New", + action_key!(km, Action::NewTab(_), to_normal!()), + ); + lp = add_shortcut( + help, + lp, + "Close", + action_key!(km, Action::CloseFocus, to_normal!()), + ); + lp = add_shortcut( + help, + lp, + "Rename", + action_key!( + km, + Action::SwitchToMode(InputMode::RenamePane), + Action::PaneNameInput(_) + ), + ); + lp = add_shortcut( + help, + lp, + "Rename", + action_key!( + km, + Action::SwitchToMode(InputMode::RenameTab), + Action::TabNameInput(_) + ), + ); + } + + // Pane keybindings + lp = add_shortcut( + help, + lp, + "Split Down", + action_key!( + km, + Action::NewPane(Some(actions::Direction::Down)), + to_normal!() + ), + ); + lp = add_shortcut( + help, + lp, + "Split Right", + action_key!( + km, + Action::NewPane(Some(actions::Direction::Right)), + to_normal!() + ), + ); + lp = add_shortcut( + help, + lp, + "Fullscreen", + action_key!(km, Action::ToggleFocusFullscreen, to_normal!()), + ); + lp = add_shortcut( + help, + lp, + "Frames", + action_key!(km, Action::TogglePaneFrames, to_normal!()), + ); + lp = add_shortcut( + help, + lp, + "Floating Toggle", + action_key!(km, Action::ToggleFloatingPanes, to_normal!()), + ); + lp = add_shortcut( + help, + lp, + "Embed Pane", + action_key!(km, Action::TogglePaneEmbedOrFloating, to_normal!()), + ); + lp = add_shortcut(help, lp, "Next", action_key!(km, Action::SwitchFocus)); + + // Tab keybindings + lp = add_shortcut( + help, + lp, + "Sync", + action_key!(km, Action::ToggleActiveSyncTab, to_normal!()), + ); + lp = add_shortcut(help, lp, "Toggle", action_key!(km, Action::ToggleTab)); + + // Resize keybindings + // By default these are defined in every mode + // Arrow keys + if help.mode == InputMode::Resize { + let arrow_keys = action_key!(km, Action::Resize(actions::ResizeDirection::Left)) + .into_iter() + .chain(action_key!(km, Action::Resize(actions::ResizeDirection::Down)).into_iter()) + .chain(action_key!(km, Action::Resize(actions::ResizeDirection::Up)).into_iter()) + .chain(action_key!(km, Action::Resize(actions::ResizeDirection::Right)).into_iter()) + .collect(); + lp = add_shortcut(help, lp, "Resize", arrow_keys); + + let pme = action_key!(km, Action::Resize(actions::ResizeDirection::Increase)) + .into_iter() + .chain( + action_key!(km, Action::Resize(actions::ResizeDirection::Decrease)).into_iter(), + ) + .collect(); + lp = add_shortcut(help, lp, "Increase/Decrease Size", pme); } + + // Move keybindings + lp = add_shortcut(help, lp, "Move", action_key!(km, Action::MovePane(Some(_)))); + lp = add_shortcut( + help, + lp, + "Next Pane", + action_key!(km, Action::MovePane(None)), + ); + + // Scroll keybindings + // arrows - Scroll + // Pg - Scroll Page + // ud - Scroll Half Page + lp = add_shortcut( + help, + lp, + "Edit Scrollback in Default Editor", + action_key!(km, Action::EditScrollback, to_normal!()), + ); + + // Session keybindings + lp = add_shortcut(help, lp, "Detach", action_key!(km, Action::Detach)); + + //for (i, (key, action)) in help.keybinds.iter().enumerate() { + // let shortcut = full_length_shortcut(i == 0, key, action, help.style.colors); + // lp.len += shortcut.len; + // lp.part = format!("{}{}", line_part.part, shortcut,); + //} let select_pane_shortcut = extra_hint_producing_function(help.style.colors); - line_part.len += select_pane_shortcut.len; - line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,); - line_part + lp.len += select_pane_shortcut.len; + lp.part = format!("{}{}", lp.part, select_pane_shortcut,); + lp } } @@ -463,19 +631,13 @@ pub fn full_tmux_mode_indication(help: &ModeInfo) -> LinePart { let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); let tmux_mode_text = "TMUX MODE"; let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text); - let mut line_part = LinePart { + let line_part = LinePart { part: format!( "{}{}{}", shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator ), len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space }; - - for (i, (letter, description)) in help.keybinds.iter().enumerate() { - let shortcut = full_length_shortcut(i == 0, letter, description, help.style.colors); - line_part.len += shortcut.len; - line_part.part = format!("{}{}", line_part.part, shortcut,); - } line_part } From 8449fdcd4f1efca0b9eaf1ebd4e25412df8cfa2b Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 28 Jun 2022 16:01:40 +0200 Subject: [PATCH 19/90] WIP: utils/keybinds: Make key order deterministic by using a BTreeMap which by default has all of its elements in sorted order internally. As of currently this doesn't seem to impress the order in which the keybindings are sent to the plugins, though. --- zellij-utils/src/input/keybinds.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index 1839a60b14..766846f276 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -1,5 +1,5 @@ //! Mapping of inputs to sequences of actions. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use super::actions::Action; use super::config; @@ -12,7 +12,7 @@ use strum::IntoEnumIterator; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Keybinds(HashMap); #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct ModeKeybinds(HashMap>); +pub struct ModeKeybinds(BTreeMap>); /// Intermediate struct used for deserialisation /// Used in the config file. @@ -227,7 +227,7 @@ impl Keybinds { impl ModeKeybinds { fn new() -> ModeKeybinds { - ModeKeybinds(HashMap::>::new()) + ModeKeybinds(BTreeMap::>::new()) } /// Merges `self` with `other`, if keys are the same, `other` overwrites. @@ -282,7 +282,7 @@ impl From for ModeKeybinds { .key .into_iter() .map(|k| (k, actions.clone())) - .collect::>>(), + .collect::>>(), ) } } From 3545fd6dd22ba7a640ce6fedd8aa2527fb4a982d Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 29 Jun 2022 10:50:26 +0200 Subject: [PATCH 20/90] utils/data: Reorder `Key` variants to have the Arrow keys sorted as "left", "down", "up", "right" in accordance with the display in e.g. the status bar. --- zellij-utils/src/data.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 63a4542c65..4df5a58a8d 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -35,13 +35,13 @@ pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) { pub enum Key { Backspace, Left, - Right, - Up, Down, + Up, + Right, Home, End, - PageUp, PageDown, + PageUp, BackTab, Delete, Insert, From c11f8cbbb75bc24014d6317afe5e57e54721ac96 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 29 Jun 2022 10:52:07 +0200 Subject: [PATCH 21/90] status-bar/first_line: Fix inverted `matches!` when trying to obtain the keybindings to switch between the input modes. Its initial purpose was to filter out all ' ', '\n' and 'Esc' keybindings for switching modes (As these are the default and not of interest for the status bar display), but it was not negated and thus only filtered out the aforementioned keys. --- default-plugins/status-bar/src/first_line.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 1c3102cdd4..43eebd89a4 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -367,7 +367,7 @@ pub fn to_char(kv: Vec) -> Key { .filter(|key| { // These are general "keybindings" to get back to normal, they aren't interesting here. // The user will figure these out for himself if he configured no other. - matches!(key, Key::Char('\n') | Key::Char(' ') | Key::Esc) + !matches!(key, Key::Char('\n') | Key::Char(' ') | Key::Esc) }) .collect::>() .into_iter() From 51aaa474a403da34deda40a581183d809ead0ee9 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 29 Jun 2022 10:54:21 +0200 Subject: [PATCH 22/90] status-bar: Don't get all modeswitch keybinds but only those that are displayed in the status bar. This currently excludes the keybindings for Entering the Pane/TabRename mode, Tmux mode and Prompt mode. We must explicitly exclude these since they aren't bound to the same Modifiers as the regular keys. Thus, if we e.g. enter Pane or Tab mode, it will pick up the `SwitchToMode(InputMode::TabRename)` action as being bound to `c`, hence the `superkey` function cannot find a common modifier, etc. But we don't display the `TabRename` input mode in the first line anyway, so we must ignore it. Therefore, we additionally add the keybinding to call the `Action::Quit` action to terminate zellij to the vector we return. Also remove the `(Key, InputMode)` tuple and convert the return type to a plain `Vec`, since the never worked with the `InputMode` in the first place. --- default-plugins/status-bar/src/first_line.rs | 43 +++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 43eebd89a4..1ec176e3a8 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -316,18 +316,50 @@ fn key_indicators( line_part } -/// Return a Vector of tuples (Key, InputMode) where each "Key" is a shortcut to switch to -/// "InputMode". -pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<(&Key, &InputMode)> { +/// Get the keybindings for switching `InputMode`s and `Quit` visible in status bar. +/// +/// Return a Vector of `Key`s where each `Key` is a shortcut to switch to some `InputMode` or Quit +/// zellij. Given the vast amount of things a user can configure in their zellij config, this +/// function has some limitations to keep in mind: +/// +/// - The vector is not deduplicated: If switching to a certain `InputMode` is bound to multiple +/// `Key`s, all of these bindings will be part of the returned vector. There is also no +/// guaranteed sort order. Which key ends up in the status bar in such a situation isn't defined. +/// - The vector will **not** contain the ' ', '\n' and 'Esc' keys: These are the default bindings +/// to get back to normal mode from any input mode, but they aren't of interest when searching +/// for the super key. If for any input mode the user has bound only these keys to switching back +/// to `InputMode::Normal`, a '?' will be displayed as keybinding instead. +pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<&Key> { mode_info .keybinds .iter() .filter_map(|(key, vac)| match vac.first() { + // No actions defined, ignore None => None, Some(vac) => { + // We ignore certain "default" keybindings that switch back to normal InputMode. + // These include: ' ', '\n', 'Esc' + if matches!(key, Key::Char(' ') | Key::Char('\n') | Key::Esc) { + return None; + } if let actions::Action::SwitchToMode(mode) = vac { - return Some((key, mode)); + return match mode { + // Store the keys that switch to displayed modes + InputMode::Normal + | InputMode::Locked + | InputMode::Pane + | InputMode::Tab + | InputMode::Resize + | InputMode::Move + | InputMode::Scroll + | InputMode::Session => Some(key), + _ => None, + }; + } + if let actions::Action::Quit = vac { + return Some(key); } + // Not a `SwitchToMode` or `Quit` action, ignore None }, }) @@ -338,9 +370,8 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) // Find a common modifier if any let mut prefix_text: &str = ""; let mut new_prefix; - for (key, _mode) in mode_switch_keys(mode_info).iter() { + for key in mode_switch_keys(mode_info).iter() { match key { - Key::F(_) => new_prefix = " F", Key::Ctrl(_) => new_prefix = " Ctrl +", Key::Alt(_) => new_prefix = " Alt +", _ => break, From d56db924def809034eed9020b59f6ebc04a10fcb Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 29 Jun 2022 10:58:33 +0200 Subject: [PATCH 23/90] status-bar/first_line: Fix output for tight screen Implement the "Squeezed" display variant where we do not display which of the modes each keybinding switches to, but only the keybinding itself. --- default-plugins/status-bar/src/first_line.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 1ec176e3a8..473a1390ae 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -160,7 +160,7 @@ fn disabled_mode_shortcut(text: &str, palette: ColoredElements, separator: &str) } fn selected_mode_shortcut_single_letter( - letter: char, + letter: &str, palette: ColoredElements, separator: &str, ) -> LinePart { @@ -182,7 +182,7 @@ fn selected_mode_shortcut_single_letter( } fn unselected_mode_shortcut_single_letter( - letter: char, + letter: &str, palette: ColoredElements, separator: &str, ) -> LinePart { @@ -204,7 +204,7 @@ fn unselected_mode_shortcut_single_letter( } fn unselected_alternate_mode_shortcut_single_letter( - letter: char, + letter: &str, palette: ColoredElements, separator: &str, ) -> LinePart { @@ -264,17 +264,18 @@ fn single_letter_ctrl_key( key: &KeyShortcut, palette: ColoredElements, separator: &str, + shared_super: bool, ) -> LinePart { - let letter_shortcut = key.letter_shortcut(false).chars().next().unwrap(); + let letter_shortcut = key.letter_shortcut(!shared_super); match key.mode { KeyMode::Unselected => { - unselected_mode_shortcut_single_letter(letter_shortcut, palette, separator) + unselected_mode_shortcut_single_letter(&letter_shortcut, palette, separator) }, KeyMode::UnselectedAlternate => { - unselected_alternate_mode_shortcut_single_letter(letter_shortcut, palette, separator) + unselected_alternate_mode_shortcut_single_letter(&letter_shortcut, palette, separator) }, KeyMode::Selected => { - selected_mode_shortcut_single_letter(letter_shortcut, palette, separator) + selected_mode_shortcut_single_letter(&letter_shortcut, palette, separator) }, KeyMode::Disabled => { disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette, separator) @@ -303,7 +304,7 @@ fn key_indicators( // Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions) line_part = LinePart::default(); for ctrl_key in keys { - let key = single_letter_ctrl_key(ctrl_key, palette, separator); + let key = single_letter_ctrl_key(ctrl_key, palette, separator, shared_super); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } From ff8ef7bfd5b8bebcd90d866b29ca40dcd8956daf Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 10:53:31 +0200 Subject: [PATCH 24/90] status-bar/second_line: Remove trailing " / " --- default-plugins/status-bar/src/second_line.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 6f92489d86..b51f262a67 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -190,7 +190,12 @@ fn select_pane_shortcut(palette: Palette) -> LinePart { } fn add_shortcut(help: &ModeInfo, mut linepart: LinePart, text: &str, keys: Vec) -> LinePart { - let shortcut = full_length_shortcut(false, keys, text, help.style.colors); + let shortcut = if linepart.len == 0 { + full_length_shortcut(true, keys, text, help.style.colors) + } else { + full_length_shortcut(false, keys, text, help.style.colors) + }; + linepart.len += shortcut.len; linepart.part = format!("{}{}", linepart.part, shortcut); linepart From 3b756332da1e598c52fca180b70aec0bbedc7396 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 10:54:44 +0200 Subject: [PATCH 25/90] status-bar/second-line: Refactor key hints Instead of determining the appropriate key hints for every case separately (i.e. enough space to show all, show shortened, shot best-effort), create a central function that returns for the current `InputMode` a Vector with tuples of: - A String to show in full-length mode - A String to show in shortened/best-effort mode - The vector of keys that goes with this key hint This allows all functions that need the hints to iterate over the vector and pick whatever hint suits them along with the Keys to display. --- default-plugins/status-bar/src/second_line.rs | 247 +++++++----------- 1 file changed, 99 insertions(+), 148 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index b51f262a67..ddcb0f4610 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -4,7 +4,6 @@ use ansi_term::{ Color::{Fixed, RGB}, Style, }; -use std::collections::VecDeque; use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; use zellij_tile_utils::palette_match; @@ -205,157 +204,13 @@ fn full_shortcut_list_nonstandard_mode( extra_hint_producing_function: fn(Palette) -> LinePart, ) -> impl FnOnce(&ModeInfo) -> LinePart { move |help| { - let km: VecDeque<(Key, Vec)> = help.keybinds.clone().into(); let mut lp = LinePart::default(); + let keys_and_hints = get_keys_and_hints(help); - if help.mode == InputMode::Pane || help.mode == InputMode::Tab { - // Shared keybindings - lp = add_shortcut( - help, - lp, - "Move Focus", - action_key!(km, Action::MoveFocus(_)), - ); - lp = add_shortcut(help, lp, "New", action_key!(km, Action::NewPane(None))); - lp = add_shortcut( - help, - lp, - "New", - action_key!(km, Action::NewTab(_), to_normal!()), - ); - lp = add_shortcut( - help, - lp, - "Close", - action_key!(km, Action::CloseFocus, to_normal!()), - ); - lp = add_shortcut( - help, - lp, - "Rename", - action_key!( - km, - Action::SwitchToMode(InputMode::RenamePane), - Action::PaneNameInput(_) - ), - ); - lp = add_shortcut( - help, - lp, - "Rename", - action_key!( - km, - Action::SwitchToMode(InputMode::RenameTab), - Action::TabNameInput(_) - ), - ); + for (long, _short, keys) in keys_and_hints.into_iter() { + lp = add_shortcut(help, lp, &long, keys.to_vec()); } - // Pane keybindings - lp = add_shortcut( - help, - lp, - "Split Down", - action_key!( - km, - Action::NewPane(Some(actions::Direction::Down)), - to_normal!() - ), - ); - lp = add_shortcut( - help, - lp, - "Split Right", - action_key!( - km, - Action::NewPane(Some(actions::Direction::Right)), - to_normal!() - ), - ); - lp = add_shortcut( - help, - lp, - "Fullscreen", - action_key!(km, Action::ToggleFocusFullscreen, to_normal!()), - ); - lp = add_shortcut( - help, - lp, - "Frames", - action_key!(km, Action::TogglePaneFrames, to_normal!()), - ); - lp = add_shortcut( - help, - lp, - "Floating Toggle", - action_key!(km, Action::ToggleFloatingPanes, to_normal!()), - ); - lp = add_shortcut( - help, - lp, - "Embed Pane", - action_key!(km, Action::TogglePaneEmbedOrFloating, to_normal!()), - ); - lp = add_shortcut(help, lp, "Next", action_key!(km, Action::SwitchFocus)); - - // Tab keybindings - lp = add_shortcut( - help, - lp, - "Sync", - action_key!(km, Action::ToggleActiveSyncTab, to_normal!()), - ); - lp = add_shortcut(help, lp, "Toggle", action_key!(km, Action::ToggleTab)); - - // Resize keybindings - // By default these are defined in every mode - // Arrow keys - if help.mode == InputMode::Resize { - let arrow_keys = action_key!(km, Action::Resize(actions::ResizeDirection::Left)) - .into_iter() - .chain(action_key!(km, Action::Resize(actions::ResizeDirection::Down)).into_iter()) - .chain(action_key!(km, Action::Resize(actions::ResizeDirection::Up)).into_iter()) - .chain(action_key!(km, Action::Resize(actions::ResizeDirection::Right)).into_iter()) - .collect(); - lp = add_shortcut(help, lp, "Resize", arrow_keys); - - let pme = action_key!(km, Action::Resize(actions::ResizeDirection::Increase)) - .into_iter() - .chain( - action_key!(km, Action::Resize(actions::ResizeDirection::Decrease)).into_iter(), - ) - .collect(); - lp = add_shortcut(help, lp, "Increase/Decrease Size", pme); - } - - // Move keybindings - lp = add_shortcut(help, lp, "Move", action_key!(km, Action::MovePane(Some(_)))); - lp = add_shortcut( - help, - lp, - "Next Pane", - action_key!(km, Action::MovePane(None)), - ); - - // Scroll keybindings - // arrows - Scroll - // Pg - Scroll Page - // ud - Scroll Half Page - lp = add_shortcut( - help, - lp, - "Edit Scrollback in Default Editor", - action_key!(km, Action::EditScrollback, to_normal!()), - ); - - // Session keybindings - lp = add_shortcut(help, lp, "Detach", action_key!(km, Action::Detach)); - - //for (i, (key, action)) in help.keybinds.iter().enumerate() { - // let shortcut = full_length_shortcut(i == 0, key, action, help.style.colors); - // lp.len += shortcut.len; - // lp.part = format!("{}{}", line_part.part, shortcut,); - //} let select_pane_shortcut = extra_hint_producing_function(help.style.colors); lp.len += select_pane_shortcut.len; lp.part = format!("{}{}", lp.part, select_pane_shortcut,); @@ -363,6 +218,102 @@ fn full_shortcut_list_nonstandard_mode( } } +/// Collect all relevant keybindings and hints to display. +/// +/// Creates a vector with tuples containing the following entries: +/// +/// - A String to display for this keybinding when there are no size restrictions, +/// - A shortened String (where sensible) to display if the whole second line becomes too long, +/// - A `Vec` of the keys that map to this keyhint +/// +/// This vector is created by iterating over the keybindings for the current [`InputMode`] and +/// storing all Keybindings that match pre-defined patterns of `Action`s. For example, the +/// `InputMode::Pane` input mode determines which keys to display for the "Move focus" hint by +/// searching the keybindings for anything that matches the `Action::MoveFocus(_)` action. Since by +/// default multiple keybindings map to some action patterns (e.g. `Action::MoveFocus(_)` is bound +/// to "hjkl", the arrow keys and "Alt + "), we deduplicate the vector of all keybindings +/// before processing it. +/// +/// Therefore we sort it by the [`Key`]s of the current keymap and deduplicate the resulting sorted +/// vector by the `Vec` action vectors bound to the keys. As such, when multiple keys map +/// to the same sequence of actions, the keys that appear first in the [`Key`] structure will be +/// displayed. +// Please don't let rustfmt play with the formatting. It will stretch out the function to about +// three times the length and all the keybinding vectors we generate become virtually unreadable +// for humans. +#[rustfmt::skip] +fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { + use Action as A; + use InputMode as IM; + use actions::Direction as Dir; + use actions::ResizeDirection as RDir; + + let mut old_keymap = mi.keybinds.clone(); + let s = |string: &str| string.to_string(); + + // 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()); + + let mut known_actions: Vec> = vec![]; + let mut km = vec![]; + for (key, acvec) in old_keymap.into_iter() { + if known_actions.contains(&acvec) { + // This action is known already + continue; + } else { + known_actions.push(acvec.to_vec()); + km.push((key, acvec)); + } + } + + return if mi.mode == IM::Pane { vec![ + (s("Move focus"), s("Move"), action_key!(km, A::MoveFocus(_))), + (s("New"), s("New"), action_key!(km, A::NewPane(None), to_normal!())), + (s("Close"), s("Close"), action_key!(km, A::CloseFocus, to_normal!())), + (s("Rename"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenamePane), A::PaneNameInput(_))), + (s("Split down"), s("Down"), action_key!(km, A::NewPane(Some(Dir::Down)), to_normal!())), + (s("Split right"), s("Right"), action_key!(km, A::NewPane(Some(Dir::Right)), to_normal!())), + (s("Fullscreen"), s("Fullscreen"), action_key!(km, A::ToggleFocusFullscreen, to_normal!())), + (s("Frames"), s("Frames"), action_key!(km, A::TogglePaneFrames, to_normal!())), + (s("Floating toggle"), s("Floating"), action_key!(km, A::ToggleFloatingPanes, to_normal!())), + (s("Embed pane"), s("Embed"), action_key!(km, A::TogglePaneEmbedOrFloating, to_normal!())), + (s("Next"), s("Next"), action_key!(km, A::SwitchFocus)), + ]} else if mi.mode == IM::Tab { vec![ + (s("Move focus"), s("Move"), action_key!(km, A::GoToPreviousTab).into_iter() + .chain(action_key!(km, A::GoToNextTab).into_iter()).collect()), + (s("New"), s("New"), action_key!(km, A::NewTab(None), to_normal!())), + (s("Close"), s("Close"), action_key!(km, A::CloseTab, to_normal!())), + (s("Rename"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenameTab), A::TabNameInput(_))), + (s("Sync"), s("Sync"), action_key!(km, A::ToggleActiveSyncTab, to_normal!())), + (s("Toggle"), s("Toggle"), action_key!(km, A::ToggleTab)), + ]} else if mi.mode == IM::Resize { vec![ + (s("Resize"), s("Resize"), action_key!(km, A::Resize(RDir::Left)).into_iter() + .chain(action_key!(km, A::Resize(RDir::Down)).into_iter()) + .chain(action_key!(km, A::Resize(RDir::Up)).into_iter()) + .chain(action_key!(km, A::Resize(RDir::Right)).into_iter()) + .collect::>()), + (s("Increase/Decrease size"), s("Increase/Decrease"), + action_key!(km, A::Resize(RDir::Increase)).into_iter() + .chain(action_key!(km, A::Resize(RDir::Decrease)).into_iter()).collect()), + ]} else if mi.mode == IM::Move { vec![ + (s("Move"), s("Move"), action_key!(km, Action::MovePane(Some(_)))), + (s("Next pane"), s("Next"), action_key!(km, Action::MovePane(None))), + ]} else if mi.mode == IM::Scroll { vec![ + (s("Scroll"), s("Scroll"), action_key!(km, Action::ScrollDown).into_iter() + .chain(action_key!(km, Action::ScrollUp).into_iter()).collect()), + (s("Scroll page"), s("Scroll"), action_key!(km, Action::PageScrollDown).into_iter() + .chain(action_key!(km, Action::PageScrollUp).into_iter()).collect()), + (s("Scroll half page"), s("Scroll"), action_key!(km, Action::HalfPageScrollDown).into_iter() + .chain(action_key!(km, Action::HalfPageScrollUp).into_iter()).collect()), + (s("Edit scrollback in default editor"), s("Edit"), + action_key!(km, Action::EditScrollback, to_normal!())), + ]} else if mi.mode == IM::Scroll { vec![ + (s("Detach"), s("Detach"), action_key!(km, Action::Detach)), + ]} else { vec![] }; +} + fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { InputMode::Normal => tip(help.style.colors), From 6a21a43d81f082a97dee60f7ac8773710e86f95e Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 10:57:35 +0200 Subject: [PATCH 26/90] status-bar/second-line: Implement shortened hints --- default-plugins/status-bar/src/second_line.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index ddcb0f4610..ae595a3c5a 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -330,11 +330,12 @@ fn shortened_shortcut_list_nonstandard_mode( ) -> impl FnOnce(&ModeInfo) -> LinePart { move |help| { let mut line_part = LinePart::default(); - for (i, (letter, description)) in help.keybinds.iter().enumerate() { - let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors); - line_part.len += shortcut.len; - line_part.part = format!("{}{}", line_part.part, shortcut,); + 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()); } + let select_pane_shortcut = extra_hint_producing_function(help.style.colors); line_part.len += select_pane_shortcut.len; line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,); From 0ba4a1b41c20c3e49b2200f6b64f80bad504a827 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 13:30:15 +0200 Subject: [PATCH 27/90] utils/data: Fix display for `Key::Alt` which previously printed only the internal char but not the modifier. --- zellij-utils/src/data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 4df5a58a8d..ad2564e089 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -74,7 +74,7 @@ impl fmt::Display for Key { '\t' => write!(f, "↹"), _ => write!(f, "{}", c), }, - Key::Alt(c) => write!(f, "{}", c), + Key::Alt(c) => write!(f, "Alt+{}", c), Key::Ctrl(c) => write!(f, "Ctrl+{}", c), Key::Null => write!(f, "NULL"), Key::Esc => write!(f, "Esc"), From c21c4c6ef3b6fe910f86dca1f06984094344315a Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 13:35:19 +0200 Subject: [PATCH 28/90] status-bar/first-line: Add hidden Tmux tile that is only shown when in Tmux mode. Note that with the default config this "breaks" the shared superkey display, because it correctly identifies that one can switch to Scroll mode via `[`. --- default-plugins/status-bar/src/first_line.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 473a1390ae..20fde095e8 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -21,6 +21,7 @@ enum KeyAction { Quit, Session, Move, + Tmux, } enum KeyMode { @@ -46,6 +47,7 @@ impl KeyShortcut { KeyAction::Quit => String::from("QUIT"), KeyAction::Session => String::from("SESSION"), KeyAction::Move => String::from("MOVE"), + KeyAction::Tmux => String::from("TMUX"), } } pub fn letter_shortcut(&self, with_prefix: bool) -> String { @@ -412,7 +414,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str, shared_super: let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); let binds = &help.keybinds; // Unselect all by default - let mut default_keys = [ + let mut default_keys = vec![ KeyShortcut::new( KeyMode::Unselected, KeyAction::Lock, @@ -475,6 +477,15 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str, shared_super: default_keys[index].key = to_char(action_key!(binds, to_normal!())); } + if help.mode == InputMode::Tmux { + // Tmux tile is hidden by default + default_keys.push(KeyShortcut::new( + KeyMode::Selected, + KeyAction::Tmux, + to_char(action_key!(binds, to_normal!())), + )); + } + key_indicators( max_len, &default_keys, From bc9533a66253a79860e3b159e3d75a14a0a50c02 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 13:41:44 +0200 Subject: [PATCH 29/90] status-bar: Print superkey as part of first line Instead of first obtaining the superkey and then the rest of the first line to display. This way we don't need to split up individual data structures and carry a boolean flag around multiple functions. It also has the advantage that when the available space is really tight, the first line is entirely empty and doesn't display a stale superkey without any other keybinding hints. --- default-plugins/status-bar/src/first_line.rs | 18 ++++++++++-------- default-plugins/status-bar/src/main.rs | 8 ++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 20fde095e8..eeb76eda69 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -290,12 +290,13 @@ fn key_indicators( keys: &[KeyShortcut], palette: ColoredElements, separator: &str, - shared_super: bool, + mode_info: &ModeInfo, ) -> LinePart { // Print full-width hints - let mut line_part = LinePart::default(); + let mut line_part = superkey(palette, separator, mode_info); + let shared_super = line_part.len > 0; for ctrl_key in keys { - let key = full_ctrl_key(ctrl_key, palette, separator, shared_super); + let key = long_tile(ctrl_key, palette, separator, shared_super); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -304,9 +305,10 @@ fn key_indicators( } // Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions) - line_part = LinePart::default(); + line_part = superkey(palette, separator, mode_info); + let shared_super = line_part.len > 0; for ctrl_key in keys { - let key = single_letter_ctrl_key(ctrl_key, palette, separator, shared_super); + let key = short_tile(ctrl_key, palette, separator, shared_super); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -392,7 +394,7 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) let suffix_separator = palette.superkey_suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[prefix, suffix_separator]).to_string(), - len: prefix_text.chars().count(), + len: prefix_text.chars().count() + separator.chars().count(), } } @@ -409,7 +411,7 @@ pub fn to_char(kv: Vec) -> Key { .unwrap_or(Key::Char('?')) } -pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str, shared_super: bool) -> LinePart { +pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { let supports_arrow_fonts = !help.capabilities.arrow_fonts; let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); let binds = &help.keybinds; @@ -491,6 +493,6 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str, shared_super: &default_keys, colored_elements, separator, - shared_super, + help, ) } diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 1348f41725..ebffa7a9d7 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -263,16 +263,12 @@ impl ZellijPlugin for State { "" }; - let colored_elements = color_elements(self.mode_info.style.colors, !supports_arrow_fonts); - let superkey = superkey(colored_elements, separator, &self.mode_info); - let ctrl_keys = ctrl_keys( + let first_line = ctrl_keys( &self.mode_info, - cols.saturating_sub(superkey.len), + cols, separator, - superkey.len > 0, ); - let first_line = format!("{}{}", superkey, ctrl_keys); let second_line = self.second_line(cols); let background = match self.mode_info.style.colors.theme_hue { From 5568673d2765ba5e017976e10762e9454d527734 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 13:43:28 +0200 Subject: [PATCH 30/90] status-bar: Rework keybinding theming Previously there were individual functions to create the tiles in the first line depending on whether: - A tile was selected, unselected, unselected alternate (for theming) or disabled, and - Tiles had full length or were displayed shortened In the first case, the functions that previously handled the theming only differed in what theme they apply to the otherwise identical content. Since the theming information was drawn from a flat structure that simulated hierarchy by giving hierarchical names to its theme "members", this couldn't be handled in code. In the second case, some of the theming information needed for the full-length shortcuts was replicated for the shortened shortcuts. Instead, rewrite the general Theming structure into a hierarchical one: Adds a new structure `SegmentStyle` that contains the style for a single segment depending on whether it is selected, unselected (alternate) or disabled. Refactor the `first-line` module to use a single function to generate either full-length or shortened tiles, that does functionally the same but switches themes based on the selection status of the tile it themes. --- default-plugins/status-bar/src/first_line.rs | 233 ++++--------------- default-plugins/status-bar/src/main.rs | 216 +++++++---------- 2 files changed, 121 insertions(+), 328 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index eeb76eda69..f06e086300 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -65,52 +65,26 @@ impl KeyShortcut { } } -fn unselected_mode_shortcut( - letter: &str, - text: &str, - palette: ColoredElements, - separator: &str, -) -> LinePart { - let prefix_separator = palette.unselected_prefix_separator.paint(separator); - let char_left_separator = palette.unselected_char_left_separator.paint(" <"); - let char_shortcut = palette.unselected_char_shortcut.paint(letter.to_string()); - let char_right_separator = palette.unselected_char_right_separator.paint(">"); - let styled_text = palette.unselected_styled_text.paint(format!("{} ", text)); - let suffix_separator = palette.unselected_suffix_separator.paint(separator); - LinePart { - part: ANSIStrings(&[ - prefix_separator, - char_left_separator, - char_shortcut, - char_right_separator, - styled_text, - suffix_separator, - ]) - .to_string(), - len: text.chars().count() + 6 + letter.len(), // 2 for the arrows, 3 for the char separators, 1 for the text padding - } -} - -fn unselected_alternate_mode_shortcut( - letter: &str, - text: &str, +fn long_tile( + key: &KeyShortcut, palette: ColoredElements, separator: &str, + shared_super: bool, ) -> LinePart { - let prefix_separator = palette - .unselected_alternate_prefix_separator - .paint(separator); - let char_left_separator = palette.unselected_alternate_char_left_separator.paint(" <"); - let char_shortcut = palette - .unselected_alternate_char_shortcut - .paint(letter.to_string()); - let char_right_separator = palette.unselected_alternate_char_right_separator.paint(">"); - let styled_text = palette - .unselected_alternate_styled_text - .paint(format!("{} ", text)); - let suffix_separator = palette - .unselected_alternate_suffix_separator - .paint(separator); + let key_hint = key.full_text(); + let key_binding= key.letter_shortcut(!shared_super); + let colors = match key.mode { + KeyMode::Unselected => palette.unselected, + KeyMode::UnselectedAlternate => palette.unselected_alternate, + KeyMode::Selected => palette.selected, + KeyMode::Disabled => palette.disabled, + }; + let prefix_separator = colors.prefix_separator.paint(separator); + let char_left_separator = colors.char_left_separator.paint(" <".to_string()); + let char_shortcut = colors.char_shortcut.paint(key_binding.to_string()); + let char_right_separator = colors.char_right_separator.paint("> ".to_string()); + let styled_text = colors.styled_text.paint(format!("{} ", key_hint)); + let suffix_separator = colors.suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[ prefix_separator, @@ -121,167 +95,44 @@ fn unselected_alternate_mode_shortcut( suffix_separator, ]) .to_string(), - len: text.chars().count() + 6 + letter.len(), // 2 for the arrows, 3 for the char separators, 1 for the text padding + len: separator.chars().count() // Separator + + 2 // " <" + + key_binding.chars().count() // Key binding + + 2 // "> " + + key_hint.chars().count() // Key hint (mode) + + 1 // " " + + separator.chars().count(), // Separator } } -fn selected_mode_shortcut( - letter: &str, - text: &str, +fn short_tile( + key: &KeyShortcut, palette: ColoredElements, separator: &str, + shared_super: bool, ) -> LinePart { - let prefix_separator = palette.selected_prefix_separator.paint(separator); - let char_left_separator = palette.selected_char_left_separator.paint(" <".to_string()); - let char_shortcut = palette.selected_char_shortcut.paint(letter.to_string()); - let char_right_separator = palette.selected_char_right_separator.paint(">".to_string()); - let styled_text = palette.selected_styled_text.paint(format!("{} ", text)); - let suffix_separator = palette.selected_suffix_separator.paint(separator); + let key_binding = key.letter_shortcut(!shared_super); + let colors = match key.mode { + KeyMode::Unselected => palette.unselected, + KeyMode::UnselectedAlternate => palette.unselected_alternate, + KeyMode::Selected => palette.selected, + KeyMode::Disabled => palette.disabled, + }; + let prefix_separator = colors.prefix_separator.paint(separator); + let char_shortcut = colors.char_shortcut.paint(format!(" {} ", key_binding.to_string())); + let suffix_separator = colors.suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[ prefix_separator, - char_left_separator, char_shortcut, - char_right_separator, - styled_text, suffix_separator, ]) .to_string(), - len: text.chars().count() + 6 + letter.len(), // 2 for the arrows, 3 for the char separators, 1 for the text padding - } -} - -fn disabled_mode_shortcut(text: &str, palette: ColoredElements, separator: &str) -> LinePart { - let prefix_separator = palette.disabled_prefix_separator.paint(separator); - let styled_text = palette.disabled_styled_text.paint(format!("{} ", text)); - let suffix_separator = palette.disabled_suffix_separator.paint(separator); - LinePart { - part: format!("{}{}{}", prefix_separator, styled_text, suffix_separator), - len: text.chars().count() + 2 + 1, // 2 for the arrows, 1 for the padding in the end - } -} - -fn selected_mode_shortcut_single_letter( - letter: &str, - palette: ColoredElements, - separator: &str, -) -> LinePart { - let char_shortcut_text = format!(" {} ", letter); - let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding - let prefix_separator = palette - .selected_single_letter_prefix_separator - .paint(separator); - let char_shortcut = palette - .selected_single_letter_char_shortcut - .paint(char_shortcut_text); - let suffix_separator = palette - .selected_single_letter_suffix_separator - .paint(separator); - LinePart { - part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), - len, - } -} - -fn unselected_mode_shortcut_single_letter( - letter: &str, - palette: ColoredElements, - separator: &str, -) -> LinePart { - let char_shortcut_text = format!(" {} ", letter); - let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding - let prefix_separator = palette - .unselected_single_letter_prefix_separator - .paint(separator); - let char_shortcut = palette - .unselected_single_letter_char_shortcut - .paint(char_shortcut_text); - let suffix_separator = palette - .unselected_single_letter_suffix_separator - .paint(separator); - LinePart { - part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), - len, - } -} - -fn unselected_alternate_mode_shortcut_single_letter( - letter: &str, - palette: ColoredElements, - separator: &str, -) -> LinePart { - let char_shortcut_text = format!(" {} ", letter); - let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding - let prefix_separator = palette - .unselected_alternate_single_letter_prefix_separator - .paint(separator); - let char_shortcut = palette - .unselected_alternate_single_letter_char_shortcut - .paint(char_shortcut_text); - let suffix_separator = palette - .unselected_alternate_single_letter_suffix_separator - .paint(separator); - LinePart { - part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), - len, - } -} - -fn full_ctrl_key( - key: &KeyShortcut, - palette: ColoredElements, - separator: &str, - shared_super: bool, -) -> LinePart { - let full_text = key.full_text(); - let letter_shortcut = key.letter_shortcut(!shared_super); - match key.mode { - KeyMode::Unselected => unselected_mode_shortcut( - &letter_shortcut, - &format!(" {}", full_text), - palette, - separator, - ), - KeyMode::UnselectedAlternate => unselected_alternate_mode_shortcut( - &letter_shortcut, - &format!(" {}", full_text), - palette, - separator, - ), - KeyMode::Selected => selected_mode_shortcut( - &letter_shortcut, - &format!(" {}", full_text), - palette, - separator, - ), - KeyMode::Disabled => disabled_mode_shortcut( - &format!(" <{}> {}", letter_shortcut, full_text), - palette, - separator, - ), - } -} - -fn single_letter_ctrl_key( - key: &KeyShortcut, - palette: ColoredElements, - separator: &str, - shared_super: bool, -) -> LinePart { - let letter_shortcut = key.letter_shortcut(!shared_super); - match key.mode { - KeyMode::Unselected => { - unselected_mode_shortcut_single_letter(&letter_shortcut, palette, separator) - }, - KeyMode::UnselectedAlternate => { - unselected_alternate_mode_shortcut_single_letter(&letter_shortcut, palette, separator) - }, - KeyMode::Selected => { - selected_mode_shortcut_single_letter(&letter_shortcut, palette, separator) - }, - KeyMode::Disabled => { - disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette, separator) - }, + len: separator.chars().count() // Separator + + 1 // " " + + key_binding.chars().count() // Key binding + + 1 // " " + + separator.chars().count(), // Separator } } diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index ebffa7a9d7..96b531c50a 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -8,7 +8,7 @@ use std::fmt::{Display, Error, Formatter}; use zellij_tile::prelude::*; use zellij_tile_utils::style; -use first_line::{ctrl_keys, superkey}; +use first_line::ctrl_keys; use second_line::{ floating_panes_are_visible, fullscreen_panes_to_hide, keybinds, locked_floating_panes_are_visible, locked_fullscreen_panes_to_hide, system_clipboard_error, @@ -45,48 +45,25 @@ impl Display for LinePart { #[derive(Clone, Copy)] pub struct ColoredElements { - // selected mode - pub selected_prefix_separator: Style, - pub selected_char_left_separator: Style, - pub selected_char_shortcut: Style, - pub selected_char_right_separator: Style, - pub selected_styled_text: Style, - pub selected_suffix_separator: Style, - // unselected mode - pub unselected_prefix_separator: Style, - pub unselected_char_left_separator: Style, - pub unselected_char_shortcut: Style, - pub unselected_char_right_separator: Style, - pub unselected_styled_text: Style, - pub unselected_suffix_separator: Style, - // unselected mode alternate color - pub unselected_alternate_prefix_separator: Style, - pub unselected_alternate_char_left_separator: Style, - pub unselected_alternate_char_shortcut: Style, - pub unselected_alternate_char_right_separator: Style, - pub unselected_alternate_styled_text: Style, - pub unselected_alternate_suffix_separator: Style, - // disabled mode - pub disabled_prefix_separator: Style, - pub disabled_styled_text: Style, - pub disabled_suffix_separator: Style, - // selected single letter - pub selected_single_letter_prefix_separator: Style, - pub selected_single_letter_char_shortcut: Style, - pub selected_single_letter_suffix_separator: Style, - // unselected single letter - pub unselected_single_letter_prefix_separator: Style, - pub unselected_single_letter_char_shortcut: Style, - pub unselected_single_letter_suffix_separator: Style, - // unselected alternate single letter - pub unselected_alternate_single_letter_prefix_separator: Style, - pub unselected_alternate_single_letter_char_shortcut: Style, - pub unselected_alternate_single_letter_suffix_separator: Style, + pub selected: SegmentStyle, + pub unselected: SegmentStyle, + pub unselected_alternate: SegmentStyle, + pub disabled: SegmentStyle, // superkey pub superkey_prefix: Style, pub superkey_suffix_separator: Style, } +#[derive(Clone, Copy)] +pub struct SegmentStyle { + pub prefix_separator: Style, + pub char_left_separator: Style, + pub char_shortcut: Style, + pub char_right_separator: Style, + pub styled_text: Style, + pub suffix_separator: Style, +} + // I really hate this, but I can't come up with a good solution for this, // we need different colors from palette for the default theme // plus here we can add new sources in the future, like Theme @@ -110,109 +87,74 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored }; match palette.source { PaletteSource::Default => ColoredElements { - selected_prefix_separator: style!(background, palette.green), - selected_char_left_separator: style!(background, palette.green).bold(), - selected_char_shortcut: style!(palette.red, palette.green).bold(), - selected_char_right_separator: style!(background, palette.green).bold(), - selected_styled_text: style!(background, palette.green).bold(), - selected_suffix_separator: style!(palette.green, background).bold(), - - unselected_prefix_separator: style!(background, palette.fg), - unselected_char_left_separator: style!(background, palette.fg).bold(), - unselected_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_char_right_separator: style!(background, palette.fg).bold(), - unselected_styled_text: style!(background, palette.fg).bold(), - unselected_suffix_separator: style!(palette.fg, background), - - unselected_alternate_prefix_separator: style!(background, alternate_background_color), - unselected_alternate_char_left_separator: style!( - background, - alternate_background_color - ) - .bold(), - unselected_alternate_char_shortcut: style!(palette.red, alternate_background_color) - .bold(), - unselected_alternate_char_right_separator: style!( - background, - alternate_background_color - ) - .bold(), - unselected_alternate_styled_text: style!(background, alternate_background_color).bold(), - unselected_alternate_suffix_separator: style!(alternate_background_color, background), - - disabled_prefix_separator: style!(background, palette.fg), - disabled_styled_text: style!(background, palette.fg).dimmed().italic(), - disabled_suffix_separator: style!(palette.fg, background), - selected_single_letter_prefix_separator: style!(background, palette.green), - selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(), - selected_single_letter_suffix_separator: style!(palette.green, background), - - unselected_single_letter_prefix_separator: style!(background, palette.fg), - unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold().dimmed(), - unselected_single_letter_suffix_separator: style!(palette.fg, background), - - unselected_alternate_single_letter_prefix_separator: style!(background, palette.fg), - unselected_alternate_single_letter_char_shortcut: style!( - palette.red, - alternate_background_color - ) - .bold() - .dimmed(), - unselected_alternate_single_letter_suffix_separator: style!(palette.fg, background), - + selected: SegmentStyle { + prefix_separator: style!(background, palette.green), + char_left_separator: style!(background, palette.green).bold(), + char_shortcut: style!(palette.red, palette.green).bold(), + char_right_separator: style!(background, palette.green).bold(), + styled_text: style!(background, palette.green).bold(), + suffix_separator: style!(palette.green, background).bold(), + }, + unselected: SegmentStyle { + prefix_separator: style!(background, palette.fg), + char_left_separator: style!(background, palette.fg).bold(), + char_shortcut: style!(palette.red, palette.fg).bold(), + char_right_separator: style!(background, palette.fg).bold(), + styled_text: style!(background, palette.fg).bold(), + suffix_separator: style!(palette.fg, background), + }, + unselected_alternate: SegmentStyle { + prefix_separator: style!(background, alternate_background_color), + char_left_separator: style!( background, alternate_background_color) .bold(), + char_shortcut: style!(palette.red, alternate_background_color) .bold(), + char_right_separator: style!( background, alternate_background_color) .bold(), + styled_text: style!(background, alternate_background_color).bold(), + suffix_separator: style!(alternate_background_color, background), + }, + disabled: SegmentStyle { + prefix_separator: style!(background, palette.fg), + char_left_separator: style!(background, palette.fg).dimmed().italic(), + char_shortcut: style!(background, palette.fg).dimmed().italic(), + char_right_separator: style!(background, palette.fg).dimmed().italic(), + styled_text: style!(background, palette.fg).dimmed().italic(), + suffix_separator: style!(palette.fg, background), + }, superkey_prefix: style!(foreground, background).bold(), superkey_suffix_separator: style!(background, background), }, PaletteSource::Xresources => ColoredElements { - selected_prefix_separator: style!(background, palette.green), - selected_char_left_separator: style!(palette.fg, palette.green).bold(), - selected_char_shortcut: style!(palette.red, palette.green).bold(), - selected_char_right_separator: style!(palette.fg, palette.green).bold(), - selected_styled_text: style!(background, palette.green).bold(), - selected_suffix_separator: style!(palette.green, background).bold(), - unselected_prefix_separator: style!(background, palette.fg), - unselected_char_left_separator: style!(background, palette.fg).bold(), - unselected_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_char_right_separator: style!(background, palette.fg).bold(), - unselected_styled_text: style!(background, palette.fg).bold(), - unselected_suffix_separator: style!(palette.fg, background), - - unselected_alternate_prefix_separator: style!(background, alternate_background_color), - unselected_alternate_char_left_separator: style!( - background, - alternate_background_color - ) - .bold(), - unselected_alternate_char_shortcut: style!(palette.red, alternate_background_color) - .bold(), - unselected_alternate_char_right_separator: style!( - background, - alternate_background_color - ) - .bold(), - unselected_alternate_styled_text: style!(background, alternate_background_color).bold(), - unselected_alternate_suffix_separator: style!(alternate_background_color, background), - - disabled_prefix_separator: style!(background, palette.fg), - disabled_styled_text: style!(background, palette.fg).dimmed(), - disabled_suffix_separator: style!(palette.fg, background), - selected_single_letter_prefix_separator: style!(palette.fg, palette.green), - selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(), - selected_single_letter_suffix_separator: style!(palette.green, palette.fg), - - unselected_single_letter_prefix_separator: style!(palette.fg, background), - unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_single_letter_suffix_separator: style!(palette.fg, background), - - unselected_alternate_single_letter_prefix_separator: style!(background, palette.fg), - unselected_alternate_single_letter_char_shortcut: style!( - palette.red, - alternate_background_color - ) - .bold() - .dimmed(), - unselected_alternate_single_letter_suffix_separator: style!(palette.fg, background), - + selected: SegmentStyle { + prefix_separator: style!(background, palette.green), + char_left_separator: style!(palette.fg, palette.green).bold(), + char_shortcut: style!(palette.red, palette.green).bold(), + char_right_separator: style!(palette.fg, palette.green).bold(), + styled_text: style!(background, palette.green).bold(), + suffix_separator: style!(palette.green, background).bold(), + }, + unselected: SegmentStyle { + prefix_separator: style!(background, palette.fg), + char_left_separator: style!(background, palette.fg).bold(), + char_shortcut: style!(palette.red, palette.fg).bold(), + char_right_separator: style!(background, palette.fg).bold(), + styled_text: style!(background, palette.fg).bold(), + suffix_separator: style!(palette.fg, background), + }, + unselected_alternate: SegmentStyle { + prefix_separator: style!(background, alternate_background_color), + char_left_separator: style!( background, alternate_background_color) .bold(), + char_shortcut: style!(palette.red, alternate_background_color) .bold(), + char_right_separator: style!( background, alternate_background_color) .bold(), + styled_text: style!(background, alternate_background_color).bold(), + suffix_separator: style!(alternate_background_color, background), + }, + disabled: SegmentStyle { + prefix_separator: style!(background, palette.fg), + char_left_separator: style!(background, palette.fg).dimmed(), + char_shortcut: style!(background, palette.fg).dimmed(), + char_right_separator: style!(background, palette.fg).dimmed(), + styled_text: style!(background, palette.fg).dimmed(), + suffix_separator: style!(palette.fg, background), + }, superkey_prefix: style!(background, palette.fg).bold(), superkey_suffix_separator: style!(palette.fg, background), }, From 8f65cf109e54727fa1e57512b8009333e1ce47de Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 13:48:59 +0200 Subject: [PATCH 31/90] status-bar/second-line: Return new `LinePart`s from the `add_shortcut` function instead of modifying the input parameters. --- default-plugins/status-bar/src/second_line.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index ae595a3c5a..3946df6d1d 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -188,16 +188,17 @@ fn select_pane_shortcut(palette: Palette) -> LinePart { show_extra_hints(palette, text_with_style.to_vec()) } -fn add_shortcut(help: &ModeInfo, mut linepart: LinePart, text: &str, keys: Vec) -> LinePart { +fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec) -> LinePart { let shortcut = if linepart.len == 0 { full_length_shortcut(true, keys, text, help.style.colors) } else { full_length_shortcut(false, keys, text, help.style.colors) }; - linepart.len += shortcut.len; - linepart.part = format!("{}{}", linepart.part, shortcut); - linepart + let mut new_linepart = LinePart::default(); + new_linepart.len += linepart.len + shortcut.len; + new_linepart.part = format!("{}{}", linepart.part, shortcut); + new_linepart } fn full_shortcut_list_nonstandard_mode( @@ -208,7 +209,7 @@ fn full_shortcut_list_nonstandard_mode( let keys_and_hints = get_keys_and_hints(help); for (long, _short, keys) in keys_and_hints.into_iter() { - lp = add_shortcut(help, lp, &long, keys.to_vec()); + lp = add_shortcut(help, &lp, &long, keys.to_vec()); } let select_pane_shortcut = extra_hint_producing_function(help.style.colors); @@ -333,7 +334,7 @@ fn shortened_shortcut_list_nonstandard_mode( 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 = add_shortcut(help, &line_part, &short, keys.to_vec()); } let select_pane_shortcut = extra_hint_producing_function(help.style.colors); From 1354bf2106d89d6dcf838b3c503ba11a22ee666b Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 13:49:46 +0200 Subject: [PATCH 32/90] status-bar/second-line: Implement adaptive behavior and make the keyhints adapt when the screen runs out of space. The hints first become shortened and when necessary partially disappear to display a "..." hint instead. --- default-plugins/status-bar/src/second_line.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 3946df6d1d..143da391b2 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -364,17 +364,19 @@ fn best_effort_shortcut_list_nonstandard_mode( ) -> impl FnOnce(&ModeInfo, usize) -> LinePart { move |help, max_len| { let mut line_part = LinePart::default(); - for (i, (letter, description)) in help.keybinds.iter().enumerate() { - let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors); - if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len { + let keys_and_hints = get_keys_and_hints(help); + + for (_, short, keys) in keys_and_hints.into_iter() { + let new_line_part = add_shortcut(help, &line_part, &short, keys.to_vec()); + if new_line_part.len + MORE_MSG.chars().count() > max_len { // TODO: better line_part.part = format!("{}{}", line_part.part, MORE_MSG); line_part.len += MORE_MSG.chars().count(); break; } - line_part.len += shortcut.len; - line_part.part = format!("{}{}", line_part.part, shortcut); + line_part = new_line_part; } + let select_pane_shortcut = extra_hint_producing_function(help.style.colors); if line_part.len + select_pane_shortcut.len <= max_len { line_part.len += select_pane_shortcut.len; From 6fd6bfb512e1baec9e605e6f41588bfd731843bc Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 15:32:41 +0200 Subject: [PATCH 33/90] status-bar/second-line: Show float pane binding based on the keycombination that's really bound to switching into the "Pane" input mode. --- default-plugins/status-bar/src/main.rs | 2 +- default-plugins/status-bar/src/second_line.rs | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 96b531c50a..7284bcd227 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -255,7 +255,7 @@ impl State { } } else if active_tab.are_floating_panes_visible { match self.mode_info.mode { - InputMode::Normal => floating_panes_are_visible(&self.mode_info.style.colors), + InputMode::Normal => floating_panes_are_visible(&self.mode_info), InputMode::Locked => { locked_floating_panes_are_visible(&self.mode_info.style.colors) }, diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 143da391b2..c6497eb536 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -502,7 +502,9 @@ pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> Line } } -pub fn floating_panes_are_visible(palette: &Palette) -> LinePart { +pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { + let palette = mode_info.style.colors; + let km = &mode_info.keybinds; let white_color = match palette.white { PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), PaletteColor::EightBit(color) => Fixed(color), @@ -519,16 +521,23 @@ pub fn floating_panes_are_visible(palette: &Palette) -> LinePart { let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); let floating_panes = "FLOATING PANES VISIBLE"; let press = "Press "; - let ctrl = "Ctrl-p "; - let plus = "+ "; + let pane_mode = format!( + "{}", + action_key!(km, Action::SwitchToMode(InputMode::Pane)) + .first() + .unwrap_or(&Key::Char('?')) + ); + let plus = ", "; let p_left_separator = "<"; + // FIXME: This is wrong. We cannot know this from normal mode, because we transfer only the + // keybindings for the current InputMode. let p = "w"; let p_right_separator = "> "; let to_hide = "to hide."; let len = floating_panes.chars().count() + press.chars().count() - + ctrl.chars().count() + + pane_mode.chars().count() + plus.chars().count() + p_left_separator.chars().count() + p.chars().count() @@ -542,7 +551,7 @@ pub fn floating_panes_are_visible(palette: &Palette) -> LinePart { Style::new().fg(orange_color).bold().paint(floating_panes), shortcut_right_separator, Style::new().fg(white_color).bold().paint(press), - Style::new().fg(green_color).bold().paint(ctrl), + Style::new().fg(green_color).bold().paint(pane_mode), Style::new().fg(white_color).bold().paint(plus), Style::new().fg(white_color).bold().paint(p_left_separator), Style::new().fg(green_color).bold().paint(p), From 47ee1116cac2e346063c7dd35bbaead7251e89c3 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 15:34:03 +0200 Subject: [PATCH 34/90] status-bar/get_keys_and_hints: Add more modes for the keybindings in Tmux and the Pane/TabRename input modes. --- default-plugins/status-bar/src/second_line.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index c6497eb536..4ab654f11e 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -310,8 +310,24 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { .chain(action_key!(km, Action::HalfPageScrollUp).into_iter()).collect()), (s("Edit scrollback in default editor"), s("Edit"), action_key!(km, Action::EditScrollback, to_normal!())), - ]} else if mi.mode == IM::Scroll { vec![ + ]} else if mi.mode == IM::Session { vec![ (s("Detach"), s("Detach"), action_key!(km, Action::Detach)), + ]} else if mi.mode == IM::Tmux { vec![ + (s("Move focus"), s("Move"), action_key!(km, A::MoveFocus(_))), + (s("Split down"), s("Down"), action_key!(km, A::NewPane(Some(Dir::Down)), to_normal!())), + (s("Split right"), s("Right"), action_key!(km, A::NewPane(Some(Dir::Right)), to_normal!())), + (s("Fullscreen"), s("Fullscreen"), action_key!(km, A::ToggleFocusFullscreen, to_normal!())), + (s("New tab"), s("New"), action_key!(km, A::NewTab(None), to_normal!())), + (s("Rename tab"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenameTab), A::TabNameInput(_))), + (s("Previous Tab"), s("Previous"), action_key!(km, A::GoToPreviousTab, to_normal!())), + (s("Next Tab"), s("Next"), action_key!(km, A::GoToNextTab, to_normal!())), + ]} else if matches!(mi.mode, IM::RenamePane | IM::RenameTab) { vec![ + // Please let's just assume nobody changes this mapping... + (s("When done"), s("Done"), vec![Key::Char('\n')]), + (s("Select pane"), s("Select"), action_key!(km, A::MoveFocusOrTab(Dir::Left)).into_iter() + .chain(action_key!(km, A::MoveFocus(Dir::Down)).into_iter()) + .chain(action_key!(km, A::MoveFocus(Dir::Up)).into_iter()) + .chain(action_key!(km, A::MoveFocusOrTab(Dir::Right)).into_iter()).collect()), ]} else { vec![] }; } From 4879ea1cf72ad21b9a0bb1bd68feea4575e5c3f8 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 15:35:31 +0200 Subject: [PATCH 35/90] status-bar/second-line: Unify mode handling and don't do extra shortcut handling for Tmux and the Pane/TabRename modes any longer. Instead, assemble this like for all other modes from the keybinding and hints vector. --- default-plugins/status-bar/src/first_line.rs | 25 ++-- default-plugins/status-bar/src/main.rs | 18 +-- default-plugins/status-bar/src/second_line.rs | 126 ------------------ 3 files changed, 15 insertions(+), 154 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index f06e086300..54039bd3ca 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -72,7 +72,7 @@ fn long_tile( shared_super: bool, ) -> LinePart { let key_hint = key.full_text(); - let key_binding= key.letter_shortcut(!shared_super); + let key_binding = key.letter_shortcut(!shared_super); let colors = match key.mode { KeyMode::Unselected => palette.unselected, KeyMode::UnselectedAlternate => palette.unselected_alternate, @@ -101,7 +101,7 @@ fn long_tile( + 2 // "> " + key_hint.chars().count() // Key hint (mode) + 1 // " " - + separator.chars().count(), // Separator + + separator.chars().count(), // Separator } } @@ -119,20 +119,17 @@ fn short_tile( KeyMode::Disabled => palette.disabled, }; let prefix_separator = colors.prefix_separator.paint(separator); - let char_shortcut = colors.char_shortcut.paint(format!(" {} ", key_binding.to_string())); + let char_shortcut = colors + .char_shortcut + .paint(format!(" {} ", key_binding.to_string())); let suffix_separator = colors.suffix_separator.paint(separator); LinePart { - part: ANSIStrings(&[ - prefix_separator, - char_shortcut, - suffix_separator, - ]) - .to_string(), + part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), len: separator.chars().count() // Separator + 1 // " " + key_binding.chars().count() // Key binding + 1 // " " - + separator.chars().count(), // Separator + + separator.chars().count(), // Separator } } @@ -339,11 +336,5 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { )); } - key_indicators( - max_len, - &default_keys, - colored_elements, - separator, - help, - ) + key_indicators(max_len, &default_keys, colored_elements, separator, help) } diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 7284bcd227..59ae157a7a 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -105,9 +105,9 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored }, unselected_alternate: SegmentStyle { prefix_separator: style!(background, alternate_background_color), - char_left_separator: style!( background, alternate_background_color) .bold(), - char_shortcut: style!(palette.red, alternate_background_color) .bold(), - char_right_separator: style!( background, alternate_background_color) .bold(), + char_left_separator: style!(background, alternate_background_color).bold(), + char_shortcut: style!(palette.red, alternate_background_color).bold(), + char_right_separator: style!(background, alternate_background_color).bold(), styled_text: style!(background, alternate_background_color).bold(), suffix_separator: style!(alternate_background_color, background), }, @@ -141,9 +141,9 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored }, unselected_alternate: SegmentStyle { prefix_separator: style!(background, alternate_background_color), - char_left_separator: style!( background, alternate_background_color) .bold(), - char_shortcut: style!(palette.red, alternate_background_color) .bold(), - char_right_separator: style!( background, alternate_background_color) .bold(), + char_left_separator: style!(background, alternate_background_color).bold(), + char_shortcut: style!(palette.red, alternate_background_color).bold(), + char_right_separator: style!(background, alternate_background_color).bold(), styled_text: style!(background, alternate_background_color).bold(), suffix_separator: style!(alternate_background_color, background), }, @@ -205,11 +205,7 @@ impl ZellijPlugin for State { "" }; - let first_line = ctrl_keys( - &self.mode_info, - cols, - separator, - ); + let first_line = ctrl_keys(&self.mode_info, cols, separator); let second_line = self.second_line(cols); diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 4ab654f11e..ac33c7d642 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -170,24 +170,6 @@ fn confirm_pane_selection(palette: Palette) -> LinePart { show_extra_hints(palette, text_with_style.to_vec()) } -/// Creates hints for usage of Rename Mode in Pane Mode -fn select_pane_shortcut(palette: Palette) -> LinePart { - use StatusBarTextBoldness::*; - use StatusBarTextColor::*; - let text_with_style = [ - (" / ", White, NotBold), - ("Alt", Orange, Bold), - (" + ", White, NotBold), - ("<", Green, Bold), - ("[]", Green, Bold), - (" or ", White, NotBold), - ("hjkl", Green, Bold), - (">", Green, Bold), - (" Select pane", White, Bold), - ]; - show_extra_hints(palette, text_with_style.to_vec()) -} - fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec) -> LinePart { let shortcut = if linepart.len == 0 { full_length_shortcut(true, keys, text, help.style.colors) @@ -335,9 +317,6 @@ fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { InputMode::Normal => tip(help.style.colors), InputMode::Locked => locked_interface_indication(help.style.colors), - InputMode::Tmux => full_tmux_mode_indication(help), - InputMode::RenamePane => full_shortcut_list_nonstandard_mode(select_pane_shortcut)(help), - InputMode::EnterSearch => full_shortcut_list_nonstandard_mode(select_pane_shortcut)(help), _ => full_shortcut_list_nonstandard_mode(confirm_pane_selection)(help), } } @@ -364,13 +343,6 @@ fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { InputMode::Normal => tip(help.style.colors), InputMode::Locked => locked_interface_indication(help.style.colors), - InputMode::Tmux => short_tmux_mode_indication(help), - InputMode::RenamePane => { - shortened_shortcut_list_nonstandard_mode(select_pane_shortcut)(help) - }, - InputMode::EnterSearch => { - shortened_shortcut_list_nonstandard_mode(select_pane_shortcut)(help) - }, _ => shortened_shortcut_list_nonstandard_mode(confirm_pane_selection)(help), } } @@ -402,22 +374,6 @@ fn best_effort_shortcut_list_nonstandard_mode( } } -fn best_effort_tmux_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart { - let mut line_part = tmux_mode_indication(help); - for (i, (letter, description)) in help.keybinds.iter().enumerate() { - let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors); - if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len { - // TODO: better - line_part.part = format!("{}{}", line_part.part, MORE_MSG); - line_part.len += MORE_MSG.chars().count(); - break; - } - line_part.len += shortcut.len; - line_part.part = format!("{}{}", line_part.part, shortcut); - } - line_part -} - fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> LinePart { match help.mode { InputMode::Normal => { @@ -436,10 +392,6 @@ fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> Lin LinePart::default() } }, - InputMode::Tmux => best_effort_tmux_shortcut_list(help, max_len), - InputMode::RenamePane => { - best_effort_shortcut_list_nonstandard_mode(select_pane_shortcut)(help, max_len) - }, _ => best_effort_shortcut_list_nonstandard_mode(confirm_pane_selection)(help, max_len), } } @@ -578,84 +530,6 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { } } -pub fn tmux_mode_indication(help: &ModeInfo) -> LinePart { - let white_color = match help.style.colors.white { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - let orange_color = match help.style.colors.orange { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - - let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" ("); - let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); - let tmux_mode_text = "TMUX MODE"; - let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text); - let line_part = LinePart { - part: format!( - "{}{}{}", - shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator - ), - len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space - }; - line_part -} - -pub fn full_tmux_mode_indication(help: &ModeInfo) -> LinePart { - let white_color = match help.style.colors.white { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - let orange_color = match help.style.colors.orange { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - - let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" ("); - let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); - let tmux_mode_text = "TMUX MODE"; - let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text); - let line_part = LinePart { - part: format!( - "{}{}{}", - shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator - ), - len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space - }; - line_part -} - -pub fn short_tmux_mode_indication(help: &ModeInfo) -> LinePart { - let white_color = match help.style.colors.white { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - let orange_color = match help.style.colors.orange { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - - let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" ("); - let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); - let tmux_mode_text = "TMUX MODE"; - let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text); - let mut line_part = LinePart { - part: format!( - "{}{}{}", - shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator - ), - len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space - }; - - for (i, (letter, description)) in help.keybinds.iter().enumerate() { - let shortcut = first_word_shortcut(i == 0, letter, description, help.style.colors); - line_part.len += shortcut.len; - line_part.part = format!("{}{}", line_part.part, shortcut); - } - line_part -} - pub fn locked_fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart { let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, From e8d3c0c9fe172b6def1378625840244e4456af1b Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 15:37:35 +0200 Subject: [PATCH 36/90] status-bar/first-line: Refactor common modifier to a separate function so it can be used by other modules, too. --- default-plugins/status-bar/src/first_line.rs | 28 +++++--------------- default-plugins/status-bar/src/main.rs | 23 ++++++++++++++++ 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 54039bd3ca..f4f35025d5 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -3,7 +3,7 @@ use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; use crate::color_elements; -use crate::{action_key, to_normal}; +use crate::{action_key, get_common_modifier, to_normal}; use crate::{ColoredElements, LinePart}; struct KeyShortcut { @@ -119,9 +119,7 @@ fn short_tile( KeyMode::Disabled => palette.disabled, }; let prefix_separator = colors.prefix_separator.paint(separator); - let char_shortcut = colors - .char_shortcut - .paint(format!(" {} ", key_binding.to_string())); + let char_shortcut = colors.char_shortcut.paint(format!(" {} ", key_binding)); let suffix_separator = colors.suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), @@ -221,24 +219,12 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<&Key> { pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) -> LinePart { // Find a common modifier if any - let mut prefix_text: &str = ""; - let mut new_prefix; - for key in mode_switch_keys(mode_info).iter() { - match key { - Key::Ctrl(_) => new_prefix = " Ctrl +", - Key::Alt(_) => new_prefix = " Alt +", - _ => break, - } - if prefix_text.is_empty() { - prefix_text = new_prefix; - } else if prefix_text != new_prefix { - // Prefix changed! - prefix_text = ""; - break; - } - } + let prefix_text = match get_common_modifier(mode_switch_keys(mode_info)) { + Some(text) => format!(" {} +", text), + _ => return LinePart::default(), + }; - let prefix = palette.superkey_prefix.paint(prefix_text); + let prefix = palette.superkey_prefix.paint(&prefix_text); let suffix_separator = palette.superkey_suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[prefix, suffix_separator]).to_string(), diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 59ae157a7a..6bd545341c 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -266,6 +266,29 @@ impl State { } } +/// Get a common modifier key from a key vector. +/// +/// Iterates over all keys, skipping keys mentioned in `to_ignore` and returns any found common +/// modifier key. +pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { + let mut modifier = ""; + let mut new_modifier; + for key in keyvec.iter() { + match key { + Key::Ctrl(_) => new_modifier = "Ctrl", + Key::Alt(_) => new_modifier = "Alt", + _ => return None, + } + if modifier.is_empty() { + modifier = new_modifier; + } else if modifier != new_modifier { + // Prefix changed! + return None; + } + } + Some(modifier.to_string()) +} + /// Get key from action pattern(s). /// /// This macro takes as arguments a `keymap` that is a `Vec<(Key, Vec)>` and contains all From b0a6ecd6fdb18494692eeecce58e785301cec662 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 30 Jun 2022 15:38:26 +0200 Subject: [PATCH 37/90] status-bar/second-line: Display modifier in hints when available. For example, for bindings to move between panes when in PaneRename mode, now displays "Alt + " instead of "". --- default-plugins/status-bar/src/second_line.rs | 92 +++++++------------ 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index ac33c7d642..1d9eb96b83 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -1,4 +1,4 @@ -use super::{action_key, to_normal}; +use super::{action_key, get_common_modifier, to_normal}; use ansi_term::{ ANSIStrings, Color::{Fixed, RGB}, @@ -17,7 +17,6 @@ use crate::{ enum StatusBarTextColor { White, Green, - Orange, } #[derive(Clone, Copy)] @@ -32,80 +31,59 @@ fn full_length_shortcut( action: &str, palette: Palette, ) -> LinePart { + if key.is_empty() { + return LinePart::default(); + } let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, ThemeHue::Light => palette.black, }); + + let modifier = match get_common_modifier(key.iter().collect()) { + Some(text) => format!("{} + ", text), + None => String::from(""), + }; let key = key .iter() - .map(|key| format!("{}", key)) + .map(|key| { + if modifier.is_empty() { + format!("{}", key) + } else { + match key { + Key::Char(c) => format!("{}", c), + Key::Alt(c) => format!("{}", c), + _ => format!("{}", key), + } + } + }) .collect::>() .join(""); - if key.is_empty() { - return LinePart { - part: "".to_string(), - len: 0, - }; - } - let green_color = palette_match!(palette.green); - let separator = if is_first_shortcut { " " } else { " / " }; - let separator = Style::new().fg(text_color).paint(separator); - let shortcut_len = key.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space - let shortcut_left_separator = Style::new().fg(text_color).paint("<"); - let shortcut = Style::new().fg(green_color).bold().paint(key); - let shortcut_right_separator = Style::new().fg(text_color).paint("> "); - let action_len = action.chars().count(); - let action = Style::new().fg(text_color).bold().paint(action); - let len = shortcut_len + action_len + separator.chars().count(); - LinePart { - part: ANSIStrings(&[ - separator, - shortcut_left_separator, - shortcut, - shortcut_right_separator, - action, - ]) - .to_string(), - len, - } -} -fn first_word_shortcut( - is_first_shortcut: bool, - key: &Key, - _action: &[Action], - palette: Palette, -) -> LinePart { - let text_color = palette_match!(match palette.theme_hue { - ThemeHue::Dark => palette.white, - ThemeHue::Light => palette.black, - }); - let letter = format!("{}", key); - let description = "test".to_string(); let green_color = palette_match!(palette.green); + let orange_color = palette_match!(palette.orange); let separator = if is_first_shortcut { " " } else { " / " }; - let separator = Style::new().fg(text_color).paint(separator); - let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space + let painted_separator = Style::new().fg(text_color).paint(separator); + let painted_modifier = Style::new().fg(orange_color).bold().paint(&modifier); let shortcut_left_separator = Style::new().fg(text_color).paint("<"); - let shortcut = Style::new().fg(green_color).bold().paint(letter); + let shortcut = Style::new().fg(green_color).bold().paint(&key); let shortcut_right_separator = Style::new().fg(text_color).paint("> "); - let description_first_word = description.split(' ').next().unwrap_or(""); - let description_first_word_length = description_first_word.chars().count(); - let description_first_word = Style::new() - .fg(text_color) - .bold() - .paint(description_first_word); - let len = shortcut_len + description_first_word_length + separator.chars().count(); + let painted_action = Style::new().fg(text_color).bold().paint(action); LinePart { part: ANSIStrings(&[ - separator, + painted_separator, + painted_modifier, shortcut_left_separator, shortcut, shortcut_right_separator, - description_first_word, + painted_action, ]) .to_string(), - len, + len: separator.chars().count() // " " or " / " + + modifier.chars().count() // Modifier (Ctrl, Alt), if any + + 1 // "<" + + key.chars().count() // The key shortcut + + 2 // "> " + + action.chars().count(), // The action associated with the key } } @@ -132,7 +110,6 @@ fn show_extra_hints( // get the colors let white_color = palette_match!(palette.white); let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); // calculate length of tipp let len = text_with_style .iter() @@ -144,7 +121,6 @@ fn show_extra_hints( let color = match color { White => white_color, Green => green_color, - Orange => orange_color, }; match is_bold { Bold => Style::new().fg(color).bold().paint(text), From 6dd8d896b04d330d237b06318ee2c771bf683e80 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 4 Jul 2022 13:15:19 +0200 Subject: [PATCH 38/90] utils/ipc: Remove `Copy` from `ClientAttributes` as preparation to add `Keybinds` as a member to the `ClientAttributes` struct. `Keybinds` contains a `HashMap`, for which the `std` doesn't derive `Copy` but only `Clone`. --- zellij-server/src/lib.rs | 10 ++++++++-- zellij-utils/src/ipc.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 7b9bf798bc..bc7d00ddbf 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -285,7 +285,7 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { let session = init_session( os_input.clone(), to_server.clone(), - client_attributes, + client_attributes.clone(), SessionOptions { opts, layout: layout.clone(), @@ -654,8 +654,14 @@ fn init_session( ); let max_panes = opts.max_panes; + let client_attributes_clone = client_attributes.clone(); move || { - screen_thread_main(screen_bus, max_panes, client_attributes, config_options); + screen_thread_main( + screen_bus, + max_panes, + client_attributes_clone, + config_options, + ); } }) .unwrap(); diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index a526897987..aa67a409c5 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -37,7 +37,7 @@ pub enum ClientType { Writer, } -#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy)] +#[derive(Default, Serialize, Deserialize, Debug, Clone)] pub struct ClientAttributes { pub size: Size, pub style: Style, From b4ae898b5d8e5f015943c0db70a78abbe1c1814f Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 4 Jul 2022 13:16:52 +0200 Subject: [PATCH 39/90] utils/input/keybinds: Fix import path Import `Key` and `InputMode` directly from `data`. --- zellij-utils/src/input/keybinds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index 766846f276..c15310c403 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap}; use super::actions::Action; use super::config; -use crate::input::{InputMode, Key}; +use crate::data::{InputMode, Key}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; From 50236ac536565bd6c603b583a1676b8ea8b4ff1e Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 4 Jul 2022 13:18:49 +0200 Subject: [PATCH 40/90] utils/ipc: Add `Keybinds` to `ClientAttributes` so we can keep track, pre-client, of the configured key bindings and pass them around further in the code. --- zellij-client/src/fake_client.rs | 1 + zellij-client/src/lib.rs | 1 + zellij-utils/src/ipc.rs | 6 ++++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/zellij-client/src/fake_client.rs b/zellij-client/src/fake_client.rs index 88b41c9e89..475298e958 100644 --- a/zellij-client/src/fake_client.rs +++ b/zellij-client/src/fake_client.rs @@ -49,6 +49,7 @@ pub fn start_fake_client( colors: palette, rounded_corners: config.ui.unwrap_or_default().pane_frames.rounded_corners, }, + keybinds: config.keybinds.clone(), }; let first_msg = ClientToServerMsg::AttachClient(client_attributes, config_options.clone()); diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 3b81694b41..c9c68db352 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -159,6 +159,7 @@ pub fn start_client( colors: palette, rounded_corners: config.ui.unwrap_or_default().pane_frames.rounded_corners, }, + keybinds: config.keybinds.clone(), }; let first_msg = match info { diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index aa67a409c5..0d5880b417 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -1,10 +1,11 @@ //! IPC stuff for starting to split things into a client and server model. - use crate::{ cli::CliArgs, data::{ClientId, InputMode, Style}, errors::{get_current_ctx, ErrorContext}, - input::{actions::Action, layout::LayoutFromYaml, options::Options, plugins::PluginsConfig}, + input::{ + actions::Action, keybinds::Keybinds, layout::LayoutFromYaml, options::Options, plugins::PluginsConfig, + }, pane_size::{Size, SizeInPixels}, }; use interprocess::local_socket::LocalSocketStream; @@ -41,6 +42,7 @@ pub enum ClientType { pub struct ClientAttributes { pub size: Size, pub style: Style, + pub keybinds: Keybinds, } #[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] From 6b1507fcf1c8ac7e5d76902ace49c9e8e5b3a979 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 4 Jul 2022 13:21:00 +0200 Subject: [PATCH 41/90] server/lib: Store `ClientAttributes` over `Style` in `SessionMetadata` to be able to pass Keybindings to other places in the code, too. Since `Style` is also a member of `ClientAttributes`, this works with minimal modifications. --- zellij-server/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index bc7d00ddbf..302a963d9f 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -104,7 +104,7 @@ impl ErrorInstruction for ServerInstruction { pub(crate) struct SessionMetaData { pub senders: ThreadSenders, pub capabilities: PluginCapabilities, - pub style: Style, + pub client_attributes: ClientAttributes, pub default_shell: Option, screen_thread: Option>, pty_thread: Option>, @@ -711,7 +711,7 @@ fn init_session( }, capabilities, default_shell, - style: client_attributes.style, + client_attributes, screen_thread: Some(screen_thread), pty_thread: Some(pty_thread), wasm_thread: Some(wasm_thread), From e0e3a5216073c206107ef45e8960f356b38043ce Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 4 Jul 2022 13:22:16 +0200 Subject: [PATCH 42/90] utils/input: Change `get_mode_info` parameters to take a `ClientAttributes` struct instead of merely the `Style` information. This way we can get the `Style` from the `ClientAttributes`, and also have access to the `keybinds` member that stores the keybinding configuration. --- zellij-server/src/lib.rs | 4 +-- zellij-server/src/route.rs | 7 ++--- zellij-server/src/screen.rs | 2 +- zellij-utils/src/input/mod.rs | 49 ++++++++++++++++++----------------- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 302a963d9f..93ea824d57 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -39,7 +39,7 @@ use zellij_utils::{ channels::{self, ChannelWithContext, SenderWithContext}, cli::CliArgs, consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE}, - data::{Event, PluginCapabilities, Style}, + data::{Event, PluginCapabilities}, errors::{ContextType, ErrorInstruction, ServerContext}, input::{ command::{RunCommand, TerminalAction}, @@ -378,7 +378,7 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .send_to_plugin(PluginInstruction::AddClient(client_id)) .unwrap(); let default_mode = options.default_mode.unwrap_or_default(); - let mode_info = get_mode_info(default_mode, attrs.style, session_data.capabilities); + let mode_info = get_mode_info(default_mode, &attrs, session_data.capabilities); let mode = mode_info.mode; session_data .senders diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index dc80814bb0..2505c4cbe9 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -75,22 +75,23 @@ fn route_action( .unwrap(); }, Action::SwitchToMode(mode) => { - let style = session.style; + let attrs = &session.client_attributes; // TODO: use the palette from the client and remove it from the server os api // this is left here as a stop gap measure until we shift some code around // to allow for this + // TODO: Need access to `ClientAttributes` here session .senders .send_to_plugin(PluginInstruction::Update( None, Some(client_id), - Event::ModeUpdate(get_mode_info(mode, style, session.capabilities)), + Event::ModeUpdate(get_mode_info(mode, attrs, session.capabilities)), )) .unwrap(); session .senders .send_to_screen(ScreenInstruction::ChangeMode( - get_mode_info(mode, style, session.capabilities), + get_mode_info(mode, attrs, session.capabilities), client_id, )) .unwrap(); diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 7b354c16d9..79cbdb5706 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -915,7 +915,7 @@ pub(crate) fn screen_thread_main( max_panes, get_mode_info( config_options.default_mode.unwrap_or_default(), - client_attributes.style, + &client_attributes, PluginCapabilities { arrow_fonts: capabilities.unwrap_or_default(), }, diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 1c34e57256..c7a80b7f4f 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -12,37 +12,38 @@ pub mod theme; #[cfg(not(target_family = "wasm"))] pub mod mouse; -use crate::{ - data::{InputMode, Key, ModeInfo, PluginCapabilities, Style}, - envs, - input::keybinds::Keybinds, -}; - -/// Creates a [`ModeInfo`] struct indicating the current [`InputMode`] and its keybinds -/// (as pairs of [`String`]s). -pub fn get_mode_info(mode: InputMode, style: Style, capabilities: PluginCapabilities) -> ModeInfo { - // FIXME: Need access to the real keybindings here - let keybinds = Keybinds::default().get_mode_keybinds(&mode).to_cloned_vec(); - let session_name = envs::get_session_name().ok(); - - ModeInfo { - mode, - keybinds, - style, - capabilities, - session_name, - } -} - #[cfg(not(target_family = "wasm"))] pub use not_wasm::*; #[cfg(not(target_family = "wasm"))] mod not_wasm { - use super::*; - use crate::data::{CharOrArrow, Direction}; + use crate::{ + data::{CharOrArrow, Direction, InputMode, Key, ModeInfo, PluginCapabilities}, + envs, + ipc::ClientAttributes, + }; use termwiz::input::{InputEvent, InputParser, KeyCode, KeyEvent, Modifiers}; + /// Creates a [`ModeInfo`] struct indicating the current [`InputMode`] and its keybinds + /// (as pairs of [`String`]s). + pub fn get_mode_info( + mode: InputMode, + attributes: &ClientAttributes, + capabilities: PluginCapabilities, + ) -> ModeInfo { + // FIXME: Need access to the real keybindings here + let keybinds = attributes.keybinds.get_mode_keybinds(&mode).to_cloned_vec(); + let session_name = envs::get_session_name().ok(); + + ModeInfo { + mode, + keybinds, + style: attributes.style, + capabilities, + session_name, + } + } + pub fn parse_keys(input_bytes: &[u8]) -> Vec { let mut ret = vec![]; let mut input_parser = InputParser::new(); // this is the termwiz InputParser From bf6b5f60dfe2ff3cf80da040d9ab1002219019d8 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 4 Jul 2022 15:18:57 +0200 Subject: [PATCH 43/90] utils/ipc: Use `rmp` for serde of IPC messages instead of `bincode`, which seemingly has issues (de)serializing `HashMap`s and `BTreeMap`s since `deserialize_any` isn't implemented for these types. --- Cargo.lock | 30 +++++++++++++++++++++++++++++- zellij-utils/Cargo.toml | 2 +- zellij-utils/src/input/mod.rs | 1 - zellij-utils/src/ipc.rs | 7 ++++--- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a3d08ab51..0b85eeaa95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1677,6 +1677,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2049,6 +2055,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "rmp" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25786b0d276110195fa3d6f3f31299900cf71dfbd6c28450f3f58a0e7f7a347e" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -3348,7 +3376,6 @@ dependencies = [ "anyhow", "async-std", "backtrace", - "bincode", "clap", "clap_complete", "colored", @@ -3364,6 +3391,7 @@ dependencies = [ "nix", "once_cell", "regex", + "rmp-serde", "serde", "serde_json", "serde_yaml", diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 28f309d338..18051ba59c 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" [dependencies] anyhow = "1.0.45" backtrace = "0.3.55" -bincode = "1.3.1" +rmp-serde = "1.1.0" clap = { version = "3.2.2", features = ["derive", "env"] } clap_complete = "3.2.1" colored = "2.0.0" diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index c7a80b7f4f..1166a3d9a0 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -31,7 +31,6 @@ mod not_wasm { attributes: &ClientAttributes, capabilities: PluginCapabilities, ) -> ModeInfo { - // FIXME: Need access to the real keybindings here let keybinds = attributes.keybinds.get_mode_keybinds(&mode).to_cloned_vec(); let session_name = envs::get_session_name().ok(); diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 0d5880b417..c0a7e26999 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -4,7 +4,8 @@ use crate::{ data::{ClientId, InputMode, Style}, errors::{get_current_ctx, ErrorContext}, input::{ - actions::Action, keybinds::Keybinds, layout::LayoutFromYaml, options::Options, plugins::PluginsConfig, + actions::Action, keybinds::Keybinds, layout::LayoutFromYaml, options::Options, + plugins::PluginsConfig, }, pane_size::{Size, SizeInPixels}, }; @@ -157,7 +158,7 @@ impl IpcSenderWithContext { /// Sends an event, along with the current [`ErrorContext`], on this [`IpcSenderWithContext`]'s socket. pub fn send(&mut self, msg: T) { let err_ctx = get_current_ctx(); - bincode::serialize_into(&mut self.sender, &(msg, err_ctx)).unwrap(); + rmp_serde::encode::write(&mut self.sender, &(msg, err_ctx)).unwrap(); // TODO: unwrapping here can cause issues when the server disconnects which we don't mind // do we need to handle errors here in other cases? let _ = self.sender.flush(); @@ -195,7 +196,7 @@ where /// Receives an event, along with the current [`ErrorContext`], on this [`IpcReceiverWithContext`]'s socket. pub fn recv(&mut self) -> Option<(T, ErrorContext)> { - match bincode::deserialize_from(&mut self.receiver) { + match rmp_serde::decode::from_read(&mut self.receiver) { Ok(msg) => Some(msg), Err(e) => { warn!("Error in IpcReceiver.recv(): {:?}", e); From 04bae2937151e9b8660401e1f38698771c093d8e Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 4 Jul 2022 20:05:44 +0200 Subject: [PATCH 44/90] fix(nix): remove `assets` from `gitignore` Remove `assets` from the gitignore of the plugins themselves, since every single plugin now depends on the asset being accessible in its source directory. --- .nix/default.nix | 25 +++++++++++++++++++++++-- .nix/plugins.nix | 1 - 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.nix/default.nix b/.nix/default.nix index c87221d814..07c0f5c185 100644 --- a/.nix/default.nix +++ b/.nix/default.nix @@ -68,6 +68,8 @@ flake-utils.lib.eachSystem [ }; cargo = rustToolchainToml; rustc = rustToolchainToml; + cargoMSRV = msrvToolchain; + rustcMSRV = msrvToolchain; buildInputs = [ # in order to run tests @@ -163,6 +165,7 @@ flake-utils.lib.eachSystem [ license = [licenses.mit]; }; in rec { + packages.default = packages.zellij-native; # crate2nix - better incremental builds, but uses ifd packages.zellij = crate2nixPkgs.callPackage ./crate2nix.nix { inherit @@ -177,7 +180,7 @@ in rec { nativeBuildInputs = nativeBuildInputs ++ defaultPlugins; }; - packages.zellij-msrv = crate2nixMsrvPkgs.callPackage ./crate2nix.nix { + packages.zellij-crate-msrv = crate2nixMsrvPkgs.callPackage ./crate2nix.nix { inherit name src @@ -204,7 +207,25 @@ in rec { ; nativeBuildInputs = nativeBuildInputs ++ defaultPlugins; }; - packages.default = packages.zellij; + # native nixpkgs support - msrv + packages.zellij-msrv = + (pkgs.makeRustPlatform { + cargo = cargoMSRV; + rustc = rustcMSRV; + }) + .buildRustPackage { + inherit + src + name + cargoLock + buildInputs + postInstall + patchPhase + desktopItems + meta + ; + nativeBuildInputs = nativeBuildInputs ++ defaultPlugins; + }; packages.plugins-compact = plugins.compact-bar; packages.plugins-status-bar = plugins.status-bar; diff --git a/.nix/plugins.nix b/.nix/plugins.nix index 270544c956..f3e1c8b7ae 100644 --- a/.nix/plugins.nix +++ b/.nix/plugins.nix @@ -10,7 +10,6 @@ ignoreSource = [ ".git" ".github" - "assets" "docs" "example" "target" From 0b3a0341cdfd3d989c30bd6ef314928cf5b013d3 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 7 Jul 2022 13:47:43 +0200 Subject: [PATCH 45/90] tests/e2e: Fix status bar in snapshots to reflect the current state of the dynamic keybindings. --- ..._terminals_vertically_when_active_terminal_is_too_small.snap | 2 +- .../e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap | 2 +- .../zellij__tests__e2e__cases__mirrored_sessions-2.snap | 2 +- .../zellij__tests__e2e__cases__scrolling_inside_a_pane.snap | 2 +- .../zellij__tests__e2e__cases__toggle_floating_panes.snap | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap index ed01a3b93a..a5e456d25d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap @@ -21,5 +21,5 @@ expression: last_snapshot │ │ │ │ └──────┘ - Ctrl + + diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap index f47db23e8a..99b43beb1f 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + Ctrl + LOCK  PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  -- INTERFACE LOCKED -- diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap index c50ca954e3..6a9555b024 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap @@ -26,4 +26,4 @@ expression: second_runner_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <←↓↑→> Move focus / New / Close / Rename / Sync / Toggle / Select pane + <←↓> Move focus / New / Close / Rename / Sync / <↹> Toggle / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap index b66ba6c569..d225756bb3 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││li█e21 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <↓↑> Scroll / Scroll / Scroll / Edit / Enter / Select pane + <↓↑> Scroll / <→←> Scroll page / Scroll half page / Edit scrollback in default editor / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap index b30448fe32..81c4b4fe1d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - (FLOATING PANES VISIBLE): Press Ctrl-p + to hide. + (FLOATING PANES VISIBLE): Press Ctrl+p, to hide. From 6a0b98258bb37e57e68b61091c07bffb4054057a Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 7 Jul 2022 14:08:48 +0200 Subject: [PATCH 46/90] status_bar/first_line: Don't show unbound modes If switching to a specific mode isn't bound to a key, don't show a tile/ribbon for it either. E.g. in `LOCKED` mode, this will only show the tile for the `LOCK` mode and ignore all others. --- default-plugins/status-bar/src/first_line.rs | 25 +++++++++++++------ .../zellij__tests__e2e__cases__lock_mode.snap | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index f4f35025d5..7f589bc83e 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -9,7 +9,7 @@ use crate::{ColoredElements, LinePart}; struct KeyShortcut { mode: KeyMode, action: KeyAction, - key: Key, + key: Option, } enum KeyAction { @@ -33,7 +33,7 @@ enum KeyMode { impl KeyShortcut { //pub fn new(mode: KeyMode, action: KeyAction, bind: KeyBind) -> Self { - pub fn new(mode: KeyMode, action: KeyAction, key: Key) -> Self { + pub fn new(mode: KeyMode, action: KeyAction, key: Option) -> Self { KeyShortcut { mode, action, key } //, bind } } @@ -51,13 +51,17 @@ impl KeyShortcut { } } pub fn letter_shortcut(&self, with_prefix: bool) -> String { + let key = match self.key { + Some(k) => k, + None => return String::from("?"), + }; if with_prefix { - format!("{}", self.key) + format!("{}", key) } else { - match self.key { + match key { Key::F(c) => format!("{}", c), Key::Ctrl(c) => format!("{}", c), - Key::Char(_) => format!("{}", self.key), + Key::Char(_) => format!("{}", key), Key::Alt(c) => format!("{}", c), _ => String::from("??"), } @@ -71,6 +75,10 @@ fn long_tile( separator: &str, shared_super: bool, ) -> LinePart { + if key.key.is_none() { + return LinePart::default(); + } + let key_hint = key.full_text(); let key_binding = key.letter_shortcut(!shared_super); let colors = match key.mode { @@ -111,6 +119,10 @@ fn short_tile( separator: &str, shared_super: bool, ) -> LinePart { + if key.key.is_none() { + return LinePart::default(); + } + let key_binding = key.letter_shortcut(!shared_super); let colors = match key.mode { KeyMode::Unselected => palette.unselected, @@ -232,7 +244,7 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) } } -pub fn to_char(kv: Vec) -> Key { +pub fn to_char(kv: Vec) -> Option { kv.into_iter() .filter(|key| { // These are general "keybindings" to get back to normal, they aren't interesting here. @@ -242,7 +254,6 @@ pub fn to_char(kv: Vec) -> Key { .collect::>() .into_iter() .next() - .unwrap_or(Key::Char('?')) } pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap index 99b43beb1f..e05e5b14d9 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK  PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + Ctrl + LOCK  -- INTERFACE LOCKED -- From 62b12ac3d05a29ad05e877b5255ad7498bf26b09 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 7 Jul 2022 14:10:24 +0200 Subject: [PATCH 47/90] =?UTF-8?q?utils/data:=20Make=20'Key::Char('=20')=20?= =?UTF-8?q?visible=20as=20"=E2=90=A3"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit so the user doesn't only see a blank char but has an idea that the space key is meant. --- zellij-utils/src/data.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index ad2564e089..36fe192d4e 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -72,6 +72,7 @@ impl fmt::Display for Key { Key::Char(c) => match c { '\n' => write!(f, "↩"), '\t' => write!(f, "↹"), + ' ' => write!(f, "␣"), _ => write!(f, "{}", c), }, Key::Alt(c) => write!(f, "Alt+{}", c), From 9bb990dbab4554225de8edf179784f8aa8a4908e Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 7 Jul 2022 15:19:56 +0200 Subject: [PATCH 48/90] status_bar/second_line: Remove extra hints generated by the `hint_producing_function` that would tell the user in every input mode how to get back to normal mode. Instead, add this as keybinding to the general keybindings vector. This removes some lines of duplicated code but most of all now applies the correct theming to this keybinding. Additionally, previously the `RenameTab` and `RenamePane` input modes would show the keybinding to get back to normal mode twice and both of them were hardcoded. This binding is now dynamically displayed based on what the user configured as keybinding. --- default-plugins/status-bar/src/main.rs | 2 +- default-plugins/status-bar/src/second_line.rs | 162 +++++------------- ...ests__e2e__cases__mirrored_sessions-2.snap | 2 +- ...__e2e__cases__scrolling_inside_a_pane.snap | 2 +- 4 files changed, 50 insertions(+), 118 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 6bd545341c..0e2d0d2c6d 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -317,7 +317,7 @@ macro_rules! action_key { /// Expands verbosely to `Action::SwitchToMode(InputMode::Normal)`, which is an action that often /// repeats in the keybindings configuration. We need it to expand to verbose rust code (i.e. a /// "Textual replacement", similar to C `#define`) so it gets picked up as proper pattern in the -/// `key_hint!` macro. +/// `action_key!` macro. #[macro_export] macro_rules! to_normal { () => { diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 1d9eb96b83..ccc822c0e4 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -13,18 +13,6 @@ use crate::{ LinePart, MORE_MSG, }; -#[derive(Clone, Copy)] -enum StatusBarTextColor { - White, - Green, -} - -#[derive(Clone, Copy)] -enum StatusBarTextBoldness { - Bold, - NotBold, -} - fn full_length_shortcut( is_first_shortcut: bool, key: Vec, @@ -101,51 +89,6 @@ fn locked_interface_indication(palette: Palette) -> LinePart { } } -fn show_extra_hints( - palette: Palette, - text_with_style: Vec<(&str, StatusBarTextColor, StatusBarTextBoldness)>, -) -> LinePart { - use StatusBarTextBoldness::*; - use StatusBarTextColor::*; - // get the colors - let white_color = palette_match!(palette.white); - let green_color = palette_match!(palette.green); - // calculate length of tipp - let len = text_with_style - .iter() - .fold(0, |len_sum, (text, _, _)| len_sum + text.chars().count()); - // apply the styles defined above - let styled_text = text_with_style - .into_iter() - .map(|(text, color, is_bold)| { - let color = match color { - White => white_color, - Green => green_color, - }; - match is_bold { - Bold => Style::new().fg(color).bold().paint(text), - NotBold => Style::new().fg(color).paint(text), - } - }) - .collect::>(); - LinePart { - part: ANSIStrings(&styled_text[..]).to_string(), - len, - } -} - -/// Creates hints for usage of Pane Mode -fn confirm_pane_selection(palette: Palette) -> LinePart { - use StatusBarTextBoldness::*; - use StatusBarTextColor::*; - let text_with_style = [ - (" / ", White, NotBold), - ("", Green, Bold), - (" Select pane", White, Bold), - ]; - show_extra_hints(palette, text_with_style.to_vec()) -} - fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec) -> LinePart { let shortcut = if linepart.len == 0 { full_length_shortcut(true, keys, text, help.style.colors) @@ -159,22 +102,14 @@ fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec new_linepart } -fn full_shortcut_list_nonstandard_mode( - extra_hint_producing_function: fn(Palette) -> LinePart, -) -> impl FnOnce(&ModeInfo) -> LinePart { - move |help| { - let mut lp = LinePart::default(); - let keys_and_hints = get_keys_and_hints(help); +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() { - lp = add_shortcut(help, &lp, &long, keys.to_vec()); - } - - let select_pane_shortcut = extra_hint_producing_function(help.style.colors); - lp.len += select_pane_shortcut.len; - lp.part = format!("{}{}", lp.part, select_pane_shortcut,); - lp + for (long, _short, keys) in keys_and_hints.into_iter() { + line_part = add_shortcut(help, &line_part, &long, keys.to_vec()); } + line_part } /// Collect all relevant keybindings and hints to display. @@ -210,6 +145,16 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { let mut old_keymap = mi.keybinds.clone(); let s = |string: &str| string.to_string(); + // Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other + // choices. Do it here before we dedupe the keymap below! + let to_normal_keys = action_key!(old_keymap, to_normal!()); + let to_normal_key = if to_normal_keys.contains(&Key::Char('\n')) { + vec![Key::Char('\n')] + } else { + // Yield `vec![key]` if `to_normal_keys` has at least one key, or an empty vec otherwise. + to_normal_keys.into_iter().take(1).collect() + }; + // 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... @@ -227,7 +172,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { } } - return if mi.mode == IM::Pane { vec![ + if mi.mode == IM::Pane { vec![ (s("Move focus"), s("Move"), action_key!(km, A::MoveFocus(_))), (s("New"), s("New"), action_key!(km, A::NewPane(None), to_normal!())), (s("Close"), s("Close"), action_key!(km, A::CloseFocus, to_normal!())), @@ -239,6 +184,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { (s("Floating toggle"), s("Floating"), action_key!(km, A::ToggleFloatingPanes, to_normal!())), (s("Embed pane"), s("Embed"), action_key!(km, A::TogglePaneEmbedOrFloating, to_normal!())), (s("Next"), s("Next"), action_key!(km, A::SwitchFocus)), + (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Tab { vec![ (s("Move focus"), s("Move"), action_key!(km, A::GoToPreviousTab).into_iter() .chain(action_key!(km, A::GoToNextTab).into_iter()).collect()), @@ -247,6 +193,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { (s("Rename"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenameTab), A::TabNameInput(_))), (s("Sync"), s("Sync"), action_key!(km, A::ToggleActiveSyncTab, to_normal!())), (s("Toggle"), s("Toggle"), action_key!(km, A::ToggleTab)), + (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Resize { vec![ (s("Resize"), s("Resize"), action_key!(km, A::Resize(RDir::Left)).into_iter() .chain(action_key!(km, A::Resize(RDir::Down)).into_iter()) @@ -256,6 +203,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { (s("Increase/Decrease size"), s("Increase/Decrease"), action_key!(km, A::Resize(RDir::Increase)).into_iter() .chain(action_key!(km, A::Resize(RDir::Decrease)).into_iter()).collect()), + (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Move { vec![ (s("Move"), s("Move"), action_key!(km, Action::MovePane(Some(_)))), (s("Next pane"), s("Next"), action_key!(km, Action::MovePane(None))), @@ -268,8 +216,10 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { .chain(action_key!(km, Action::HalfPageScrollUp).into_iter()).collect()), (s("Edit scrollback in default editor"), s("Edit"), action_key!(km, Action::EditScrollback, to_normal!())), + (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Session { vec![ (s("Detach"), s("Detach"), action_key!(km, Action::Detach)), + (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Tmux { vec![ (s("Move focus"), s("Move"), action_key!(km, A::MoveFocus(_))), (s("Split down"), s("Down"), action_key!(km, A::NewPane(Some(Dir::Down)), to_normal!())), @@ -279,75 +229,57 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { (s("Rename tab"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenameTab), A::TabNameInput(_))), (s("Previous Tab"), s("Previous"), action_key!(km, A::GoToPreviousTab, to_normal!())), (s("Next Tab"), s("Next"), action_key!(km, A::GoToNextTab, to_normal!())), + (s("Select pane"), s("Select"), to_normal_key), ]} else if matches!(mi.mode, IM::RenamePane | IM::RenameTab) { vec![ - // Please let's just assume nobody changes this mapping... - (s("When done"), s("Done"), vec![Key::Char('\n')]), + (s("When done"), s("Done"), to_normal_key), (s("Select pane"), s("Select"), action_key!(km, A::MoveFocusOrTab(Dir::Left)).into_iter() .chain(action_key!(km, A::MoveFocus(Dir::Down)).into_iter()) .chain(action_key!(km, A::MoveFocus(Dir::Up)).into_iter()) .chain(action_key!(km, A::MoveFocusOrTab(Dir::Right)).into_iter()).collect()), - ]} else { vec![] }; + ]} else { vec![] } } fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { InputMode::Normal => tip(help.style.colors), InputMode::Locked => locked_interface_indication(help.style.colors), - _ => full_shortcut_list_nonstandard_mode(confirm_pane_selection)(help), + _ => full_shortcut_list_nonstandard_mode(help), } } -fn shortened_shortcut_list_nonstandard_mode( - extra_hint_producing_function: fn(Palette) -> LinePart, -) -> impl FnOnce(&ModeInfo) -> LinePart { - move |help| { - 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()); - } +fn shortened_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart { + let mut line_part = LinePart::default(); + let keys_and_hints = get_keys_and_hints(help); - let select_pane_shortcut = extra_hint_producing_function(help.style.colors); - line_part.len += select_pane_shortcut.len; - line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,); - line_part + 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 { match help.mode { InputMode::Normal => tip(help.style.colors), InputMode::Locked => locked_interface_indication(help.style.colors), - _ => shortened_shortcut_list_nonstandard_mode(confirm_pane_selection)(help), + _ => shortened_shortcut_list_nonstandard_mode(help), } } -fn best_effort_shortcut_list_nonstandard_mode( - extra_hint_producing_function: fn(Palette) -> LinePart, -) -> impl FnOnce(&ModeInfo, usize) -> LinePart { - move |help, max_len| { - let mut line_part = LinePart::default(); - let keys_and_hints = get_keys_and_hints(help); - - for (_, short, keys) in keys_and_hints.into_iter() { - let new_line_part = add_shortcut(help, &line_part, &short, keys.to_vec()); - if new_line_part.len + MORE_MSG.chars().count() > max_len { - // TODO: better - line_part.part = format!("{}{}", line_part.part, MORE_MSG); - line_part.len += MORE_MSG.chars().count(); - break; - } - line_part = new_line_part; - } - - let select_pane_shortcut = extra_hint_producing_function(help.style.colors); - if line_part.len + select_pane_shortcut.len <= max_len { - line_part.len += select_pane_shortcut.len; - line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,); +fn best_effort_shortcut_list_nonstandard_mode(help: &ModeInfo, max_len: usize) -> 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() { + let new_line_part = add_shortcut(help, &line_part, &short, keys.to_vec()); + if new_line_part.len + MORE_MSG.chars().count() > max_len { + // TODO: better + line_part.part = format!("{}{}", line_part.part, MORE_MSG); + line_part.len += MORE_MSG.chars().count(); + break; } - line_part + line_part = new_line_part; } + line_part } fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> LinePart { @@ -368,7 +300,7 @@ fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> Lin LinePart::default() } }, - _ => best_effort_shortcut_list_nonstandard_mode(confirm_pane_selection)(help, max_len), + _ => best_effort_shortcut_list_nonstandard_mode(help, max_len), } } diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap index 6a9555b024..47038e6ae7 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap @@ -26,4 +26,4 @@ expression: second_runner_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <←↓> Move focus / New / Close / Rename / Sync / <↹> Toggle / Select pane + <←↓> Move focus / New / Close / Rename / Sync / <↹> Toggle / <↩> Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap index d225756bb3..6c78232906 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││li█e21 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <↓↑> Scroll / <→←> Scroll page / Scroll half page / Edit scrollback in default editor / Select pane + <↓↑> Scroll / <→←> Scroll page / Scroll half page / Edit scrollback in default editor / <↩> Select pane From f1bf0d2a1d395ae074e71cf4e02445a5b0d7ec1f Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 8 Jul 2022 13:46:22 +0200 Subject: [PATCH 49/90] utils/data: format unprintable chars as words MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit instead of unicode symbols. E.g. write "SPACE" instead of "␣". --- .../zellij__tests__e2e__cases__mirrored_sessions-2.snap | 2 +- ...llij__tests__e2e__cases__scrolling_inside_a_pane.snap | 2 +- zellij-utils/src/data.rs | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap index 47038e6ae7..3d4c7368e2 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap @@ -26,4 +26,4 @@ expression: second_runner_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <←↓> Move focus / New / Close / Rename / Sync / <↹> Toggle / <↩> Select pane + <←↓> Move focus / New / Close / Rename / Sync / Toggle / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap index 6c78232906..34630721b9 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││li█e21 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <↓↑> Scroll / <→←> Scroll page / Scroll half page / Edit scrollback in default editor / <↩> Select pane + <↓↑> Scroll / <→←> Scroll page / Scroll half page / Edit scrollback in default editor / Select pane diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 36fe192d4e..f6c4d829df 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -31,6 +31,9 @@ pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) { (colors.green, colors.black) } +// TODO: Add a shortened string representation (beyond `Display::fmt` below) that can be used when +// screen space is scarce. Useful for e.g. "ENTER", "SPACE", "TAB" to display as Unicode +// representations instead. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] pub enum Key { Backspace, @@ -70,9 +73,9 @@ impl fmt::Display for Key { Key::Insert => write!(f, "Ins"), Key::F(n) => write!(f, "F{}", n), Key::Char(c) => match c { - '\n' => write!(f, "↩"), - '\t' => write!(f, "↹"), - ' ' => write!(f, "␣"), + '\n' => write!(f, "ENTER"), + '\t' => write!(f, "TAB"), + ' ' => write!(f, "SPACE"), _ => write!(f, "{}", c), }, Key::Alt(c) => write!(f, "Alt+{}", c), From 05962d0667cc3537d54084763f37987ef74ba1f4 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Sun, 10 Jul 2022 08:37:17 +0200 Subject: [PATCH 50/90] utils/data: Fix display for `Ctrl`/`Alt` keys previously their "inner" chars would be displayed with a regular `fmt::Display` for the `&str` type they are. This doesn't match what we want to output. So instead we wrap the inner chars into `Key::Char` before printing them. --- zellij-utils/src/data.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index f6c4d829df..4c1e8abaf8 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -64,13 +64,13 @@ impl fmt::Display for Key { Key::Right => write!(f, "{}", Direction::Right), Key::Up => write!(f, "{}", Direction::Up), Key::Down => write!(f, "{}", Direction::Down), - Key::Home => write!(f, "Home"), - Key::End => write!(f, "End"), + Key::Home => write!(f, "HOME"), + Key::End => write!(f, "END"), Key::PageUp => write!(f, "PgUp"), - Key::PageDown => write!(f, "PgDown"), - Key::BackTab => write!(f, "BackTab"), - Key::Delete => write!(f, "Del"), - Key::Insert => write!(f, "Ins"), + Key::PageDown => write!(f, "PgDn"), + Key::BackTab => write!(f, "TAB"), + Key::Delete => write!(f, "DEL"), + Key::Insert => write!(f, "INS"), Key::F(n) => write!(f, "F{}", n), Key::Char(c) => match c { '\n' => write!(f, "ENTER"), @@ -79,7 +79,7 @@ impl fmt::Display for Key { _ => write!(f, "{}", c), }, Key::Alt(c) => write!(f, "Alt+{}", c), - Key::Ctrl(c) => write!(f, "Ctrl+{}", c), + Key::Ctrl(c) => write!(f, "Ctrl+{}", Key::Char(*c)), Key::Null => write!(f, "NULL"), Key::Esc => write!(f, "Esc"), } @@ -96,7 +96,7 @@ pub enum CharOrArrow { impl fmt::Display for CharOrArrow { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - CharOrArrow::Char(c) => write!(f, "{}", c), + CharOrArrow::Char(c) => write!(f, "{}", Key::Char(*c)), CharOrArrow::Direction(d) => write!(f, "{}", d), } } From 36f5feae45426983801eec46419ed5e70c0bf1d2 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Sun, 10 Jul 2022 08:38:32 +0200 Subject: [PATCH 51/90] utils/data: Change order of `Key`s so that e.g. for the default bindings in `Scroll` mode we prefer to show `PgDn|PgUp` rather than the arrow keys these actions are bound to as well. --- zellij-utils/src/data.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 4c1e8abaf8..c3f996aa2d 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -34,24 +34,26 @@ pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) { // TODO: Add a shortened string representation (beyond `Display::fmt` below) that can be used when // screen space is scarce. Useful for e.g. "ENTER", "SPACE", "TAB" to display as Unicode // representations instead. +// NOTE: Do not reorder the key variants since that influences what the `status_bar` plugin +// displays! #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] pub enum Key { - Backspace, + PageDown, + PageUp, Left, Down, Up, Right, Home, End, - PageDown, - PageUp, - BackTab, + Backspace, Delete, Insert, F(u8), Char(char), Alt(CharOrArrow), Ctrl(char), + BackTab, Null, Esc, } From 636fb3c3a3d64b01463a1f6be6b1a46024fe9bae Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Sun, 10 Jul 2022 08:39:38 +0200 Subject: [PATCH 52/90] status_bar/first_line: Don't ignore default char bindings by default. These include the '\n', ' ' and 'Esc' bindings that by default lead back to `Normal` input mode from all the modes. Previously we would unconditionally ignore them and consequently not print the tile when in fact the user may have bound this particular action to either of the keys. Instead now we first ignore the keys mentioned and if we turn up with an undefined binding, we consider these default keys as well so we get *something* to display in any case. --- default-plugins/status-bar/src/first_line.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 7f589bc83e..3fa2d1011d 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -245,15 +245,20 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) } pub fn to_char(kv: Vec) -> Option { - kv.into_iter() + let key = kv + .iter() .filter(|key| { // These are general "keybindings" to get back to normal, they aren't interesting here. - // The user will figure these out for himself if he configured no other. !matches!(key, Key::Char('\n') | Key::Char(' ') | Key::Esc) }) - .collect::>() + .collect::>() .into_iter() - .next() + .next(); + // Maybe the user bound one of the ignored keys? + if key.is_none() { + return kv.get(0).cloned(); + } + key.cloned() } pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { From 13af73db41304ba04ecc09275730c4593e95d7eb Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Sun, 10 Jul 2022 08:41:41 +0200 Subject: [PATCH 53/90] status_bar/first_line: Add space when no modifier is shared between the keybindings. This way there isn't a stray arrow at the very border of the screen, but it is spaced just like the tab-bar and the second line is. --- default-plugins/status-bar/src/first_line.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 3fa2d1011d..ff6ab0e254 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -152,7 +152,7 @@ fn key_indicators( ) -> LinePart { // Print full-width hints let mut line_part = superkey(palette, separator, mode_info); - let shared_super = line_part.len > 0; + let shared_super = line_part.len > 1; for ctrl_key in keys { let key = long_tile(ctrl_key, palette, separator, shared_super); line_part.part = format!("{}{}", line_part.part, key.part); @@ -164,7 +164,7 @@ fn key_indicators( // Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions) line_part = superkey(palette, separator, mode_info); - let shared_super = line_part.len > 0; + let shared_super = line_part.len > 1; for ctrl_key in keys { let key = short_tile(ctrl_key, palette, separator, shared_super); line_part.part = format!("{}{}", line_part.part, key.part); @@ -233,7 +233,12 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) // Find a common modifier if any let prefix_text = match get_common_modifier(mode_switch_keys(mode_info)) { Some(text) => format!(" {} +", text), - _ => return LinePart::default(), + _ => { + return LinePart { + part: palette.superkey_prefix.paint(" ").to_string(), + len: 1, + } + }, }; let prefix = palette.superkey_prefix.paint(&prefix_text); From 76dfc5c97cdbf30c86197d319ba5652f8d59a498 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Sun, 10 Jul 2022 08:42:27 +0200 Subject: [PATCH 54/90] status_bar/second_line: Print separators between consecutive keys bound to specific actions. This allows the user to visually differ between different keys. --- default-plugins/status-bar/src/second_line.rs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index ccc822c0e4..e9819139b0 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -44,32 +44,31 @@ fn full_length_shortcut( } } }) - .collect::>() - .join(""); + .collect::>(); let green_color = palette_match!(palette.green); let orange_color = palette_match!(palette.orange); let separator = if is_first_shortcut { " " } else { " / " }; - let painted_separator = Style::new().fg(text_color).paint(separator); - let painted_modifier = Style::new().fg(orange_color).bold().paint(&modifier); - let shortcut_left_separator = Style::new().fg(text_color).paint("<"); - let shortcut = Style::new().fg(green_color).bold().paint(&key); - let shortcut_right_separator = Style::new().fg(text_color).paint("> "); - let painted_action = Style::new().fg(text_color).bold().paint(action); + + let mut ansi_string = vec![ + Style::new().fg(text_color).paint(separator), + Style::new().fg(orange_color).bold().paint(&modifier), + Style::new().fg(text_color).paint("<"), + ]; + for (idx, key) in key.iter().enumerate() { + if idx > 0 { + ansi_string.push(Style::new().fg(text_color).paint("|")); + } + ansi_string.push(Style::new().fg(green_color).bold().paint(key)); + } + ansi_string.push(Style::new().fg(text_color).paint("> ")); + ansi_string.push(Style::new().fg(text_color).bold().paint(action)); LinePart { - part: ANSIStrings(&[ - painted_separator, - painted_modifier, - shortcut_left_separator, - shortcut, - shortcut_right_separator, - painted_action, - ]) - .to_string(), + part: ANSIStrings(&ansi_string).to_string(), len: separator.chars().count() // " " or " / " + modifier.chars().count() // Modifier (Ctrl, Alt), if any + 1 // "<" - + key.chars().count() // The key shortcut + + key.join("/").chars().count() // The key shortcut + 2 // "> " + action.chars().count(), // The action associated with the key } From a92ae99c916350b08d4ea31fb545e1334a76d400 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Sun, 10 Jul 2022 09:00:13 +0200 Subject: [PATCH 55/90] status_bar/main: Don't return modifier if empty --- default-plugins/status-bar/src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 0e2d0d2c6d..234e6ede11 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -286,7 +286,10 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { return None; } } - Some(modifier.to_string()) + match modifier.is_empty() { + true => None, + false => Some(modifier.to_string()), + } } /// Get key from action pattern(s). From 4815ba34e8643dd35a3722c04ca39db0fc3d400f Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 13 Jul 2022 19:42:55 +0200 Subject: [PATCH 56/90] status_bar/first_line: Don't suppress Disabled tiles Disabled is a special state that the keybindings only assume in locked mode. It turns the respective tiles grey to signal to the user that these are currently inactive. With respect to users new to zellij, it may appear confusing that when entering locked mode all the other tiles disappear (which they do because they have no valid keybinding assigned). Since we have no keybinding for them, we still display them but without any associated key (i.e. as `<>` for the binding). --- default-plugins/status-bar/src/first_line.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index ff6ab0e254..0edff4d9aa 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -75,12 +75,15 @@ fn long_tile( separator: &str, shared_super: bool, ) -> LinePart { - if key.key.is_none() { + let key_hint = key.full_text(); + let key_binding = if let KeyMode::Disabled = key.mode { + "".to_string() + } else if key.key.is_none() { return LinePart::default(); - } + } else { + key.letter_shortcut(!shared_super) + }; - let key_hint = key.full_text(); - let key_binding = key.letter_shortcut(!shared_super); let colors = match key.mode { KeyMode::Unselected => palette.unselected, KeyMode::UnselectedAlternate => palette.unselected_alternate, @@ -233,12 +236,7 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) // Find a common modifier if any let prefix_text = match get_common_modifier(mode_switch_keys(mode_info)) { Some(text) => format!(" {} +", text), - _ => { - return LinePart { - part: palette.superkey_prefix.paint(" ").to_string(), - len: 1, - } - }, + _ => return LinePart::default(), }; let prefix = palette.superkey_prefix.paint(&prefix_text); From 1969dd0967684e4b6972fd775d23ae916375b192 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 13 Jul 2022 19:45:44 +0200 Subject: [PATCH 57/90] status_bar/first_line: Don't print leading triangle on first tile, when there is no shared superkey. --- default-plugins/status-bar/src/first_line.rs | 28 ++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 0edff4d9aa..af7d1d3e2c 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -74,6 +74,7 @@ fn long_tile( palette: ColoredElements, separator: &str, shared_super: bool, + first_tile: bool, ) -> LinePart { let key_hint = key.full_text(); let key_binding = if let KeyMode::Disabled = key.mode { @@ -90,7 +91,12 @@ fn long_tile( KeyMode::Selected => palette.selected, KeyMode::Disabled => palette.disabled, }; - let prefix_separator = colors.prefix_separator.paint(separator); + let start_separator = if !shared_super && first_tile { + "" + } else { + separator + }; + let prefix_separator = colors.prefix_separator.paint(start_separator); let char_left_separator = colors.char_left_separator.paint(" <".to_string()); let char_shortcut = colors.char_shortcut.paint(key_binding.to_string()); let char_right_separator = colors.char_right_separator.paint("> ".to_string()); @@ -106,12 +112,12 @@ fn long_tile( suffix_separator, ]) .to_string(), - len: separator.chars().count() // Separator - + 2 // " <" - + key_binding.chars().count() // Key binding - + 2 // "> " - + key_hint.chars().count() // Key hint (mode) - + 1 // " " + len: start_separator.chars().count() // Separator + + 2 // " <" + + key_binding.chars().count() // Key binding + + 2 // "> " + + key_hint.chars().count() // Key hint (mode) + + 1 // " " + separator.chars().count(), // Separator } } @@ -155,9 +161,9 @@ fn key_indicators( ) -> LinePart { // Print full-width hints let mut line_part = superkey(palette, separator, mode_info); - let shared_super = line_part.len > 1; - for ctrl_key in keys { - let key = long_tile(ctrl_key, palette, separator, shared_super); + let shared_super = line_part.len > 0; + for (i, ctrl_key) in keys.iter().enumerate() { + let key = long_tile(ctrl_key, palette, separator, shared_super, i == 0); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -167,7 +173,7 @@ fn key_indicators( // Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions) line_part = superkey(palette, separator, mode_info); - let shared_super = line_part.len > 1; + let shared_super = line_part.len > 0; for ctrl_key in keys { let key = short_tile(ctrl_key, palette, separator, shared_super); line_part.part = format!("{}{}", line_part.part, key.part); From 5d1a7ee63df2fc1ba48f6f31a55bc1b9dda19805 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 13 Jul 2022 19:46:08 +0200 Subject: [PATCH 58/90] status_bar/second_line: Add exceptions for inter-key separators. Keeps groups of `hjkl` and arrow keys intact (doesn't add separators between the keys) but separates all others. --- default-plugins/status-bar/src/second_line.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index e9819139b0..4f1f680741 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -55,9 +55,17 @@ fn full_length_shortcut( Style::new().fg(orange_color).bold().paint(&modifier), Style::new().fg(text_color).paint("<"), ]; + let key_string = key.join(""); + let key_separator = match &key_string[..] { + "hjkl" => "", + "←↓↑→" => "", + "←↓" => "", + "↓↑" => "", + _ => "|", + }; for (idx, key) in key.iter().enumerate() { - if idx > 0 { - ansi_string.push(Style::new().fg(text_color).paint("|")); + if idx > 0 && !key_separator.is_empty() { + ansi_string.push(Style::new().fg(text_color).paint(key_separator)); } ansi_string.push(Style::new().fg(green_color).bold().paint(key)); } From 07d0d60757a86690b8c6ded40b52a5eab24205f6 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 13:27:17 +0200 Subject: [PATCH 59/90] status_bar/main: Refactor `action_key` to a regular function instead of a macro. It turns out that while being able to match patterns is a nice feature, we completely rely on the keys that drop out of the pattern found this way to be sorted in a sensible way. Since we sort the key vectors in the necessary places after the keys, and not the actions, this of course doesn't apply when the user changes "hjkl" to "zjkl", which would then become "jklz". Now this is of course wrong, because "z" still means "Move focus left", and not "Move focus right". With the function we now assume a slice of Actions that we match the action vectors from the keybindings against to obtain the necessary keys. In order to avoid ugly `into_iter().chain(...)` constructs we had before we also add a new function `action_key_group` that takes a sliced array of slices to get a whole group of keys to display. --- default-plugins/status-bar/src/main.rs | 44 ++++++++++++-------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 234e6ede11..59889fabbc 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -6,6 +6,7 @@ use ansi_term::Style; use std::fmt::{Display, Error, Formatter}; use zellij_tile::prelude::*; +use zellij_tile::prelude::actions::Action; use zellij_tile_utils::style; use first_line::ctrl_keys; @@ -300,30 +301,25 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { /// trigger the action pattern are returned as vector of `Vec`. // TODO: Accept multiple sequences of patterns, possible separated by '|', and bin them together // into one group under 'text'. -#[macro_export] -macro_rules! action_key { - ($keymap:ident, $( $p:pat ),+) => { - //let mut ret: Vec; - $keymap.iter(). - filter_map(|(key, acvec)| { - match matches!(acvec.as_slice(), &[$($p),+]) { - true => Some(*key), - false => None - } - }) - .collect::>() - }; +pub fn action_key(keymap: &Vec<(Key, Vec)>, action: &[Action]) -> Vec { + keymap.iter(). + filter_map(|(key, acvec)| { + if acvec.as_slice() == action { + Some(*key) + } else { + None + } + }) + .collect::>() } -/// Helper macro to represent common pattern. -/// -/// Expands verbosely to `Action::SwitchToMode(InputMode::Normal)`, which is an action that often -/// repeats in the keybindings configuration. We need it to expand to verbose rust code (i.e. a -/// "Textual replacement", similar to C `#define`) so it gets picked up as proper pattern in the -/// `action_key!` macro. -#[macro_export] -macro_rules! to_normal { - () => { - Action::SwitchToMode(InputMode::Normal) - }; +pub fn action_key_group(keymap: &Vec<(Key, Vec)>, actions: &[&[Action]]) -> Vec { + let mut ret = vec![]; + for action in actions { + ret.extend(action_key(&keymap, action)); + } + ret } + +/// Shorthand for `Action::SwitchToMode(InputMode::Normal)`. +pub const TO_NORMAL: Action = Action::SwitchToMode(InputMode::Normal); From 0acbe10acca543048b860e2cb0c65b5043c51a1e Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 13:31:25 +0200 Subject: [PATCH 60/90] status_bar/first_line: Fix "triangle" for short tiles since we do not want to display a colored triangle at the start of the line when in sortened mode (just as we do for the long tiles now). Also fix a bug that would make the triangle reappear when the first keybinding to be displayed didn't have a key assigned and thus wouldn't be displayed at all. --- default-plugins/status-bar/src/first_line.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index af7d1d3e2c..5b92309c1f 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -127,6 +127,7 @@ fn short_tile( palette: ColoredElements, separator: &str, shared_super: bool, + first_tile: bool, ) -> LinePart { if key.key.is_none() { return LinePart::default(); @@ -139,7 +140,12 @@ fn short_tile( KeyMode::Selected => palette.selected, KeyMode::Disabled => palette.disabled, }; - let prefix_separator = colors.prefix_separator.paint(separator); + let start_separator = if !shared_super && first_tile { + "" + } else { + separator + }; + let prefix_separator = colors.prefix_separator.paint(start_separator); let char_shortcut = colors.char_shortcut.paint(format!(" {} ", key_binding)); let suffix_separator = colors.suffix_separator.paint(separator); LinePart { @@ -162,8 +168,9 @@ fn key_indicators( // Print full-width hints let mut line_part = superkey(palette, separator, mode_info); let shared_super = line_part.len > 0; - for (i, ctrl_key) in keys.iter().enumerate() { - let key = long_tile(ctrl_key, palette, separator, shared_super, i == 0); + for ctrl_key in keys { + let line_empty = line_part.len == 0; + let key = long_tile(ctrl_key, palette, separator, shared_super, line_empty); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -175,7 +182,8 @@ fn key_indicators( line_part = superkey(palette, separator, mode_info); let shared_super = line_part.len > 0; for ctrl_key in keys { - let key = short_tile(ctrl_key, palette, separator, shared_super); + let line_empty = line_part.len == 0; + let key = short_tile(ctrl_key, palette, separator, shared_super, line_empty); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } From 455b75b6dde5c654e65835802b601cd9b245f211 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 13:32:58 +0200 Subject: [PATCH 61/90] status_bar/second_line: Fix typo that would cause single `Ctrl+?` bindings for actions in the second line to be displayed as `Ctrl + `. --- default-plugins/status-bar/src/second_line.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 4f1f680741..fc93ee15f7 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -38,7 +38,7 @@ fn full_length_shortcut( format!("{}", key) } else { match key { - Key::Char(c) => format!("{}", c), + Key::Ctrl(c) => format!("{}", c), Key::Alt(c) => format!("{}", c), _ => format!("{}", key), } From 4715cd57e4f8ea0bf9662eb7da4a6490e8bef4e5 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 13:35:01 +0200 Subject: [PATCH 62/90] status_bar/second_line: Fix char count when displaying groups of keys in a binding with or without a separator. --- default-plugins/status-bar/src/second_line.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index fc93ee15f7..f32624ceaa 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -76,7 +76,7 @@ fn full_length_shortcut( len: separator.chars().count() // " " or " / " + modifier.chars().count() // Modifier (Ctrl, Alt), if any + 1 // "<" - + key.join("/").chars().count() // The key shortcut + + key.join(key_separator).chars().count() // The key shortcut + 2 // "> " + action.chars().count(), // The action associated with the key } From ead39c39fbc283863a7347d3745acf19f649f7cf Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 13:36:23 +0200 Subject: [PATCH 63/90] status_bar: Use new `action_key` fn instead of the previous macro to obtain the keys to display in the status bar in a fixed given order. Also fix the display "bug" where tab switching would be shows as "ArrowLeft/ArrowDown" instead of "ArrowLeft/ArrowRight". --- default-plugins/status-bar/src/first_line.rs | 38 ++--- default-plugins/status-bar/src/second_line.rs | 134 ++++++++++-------- 2 files changed, 96 insertions(+), 76 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 5b92309c1f..196464405e 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -3,7 +3,7 @@ use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; use crate::color_elements; -use crate::{action_key, get_common_modifier, to_normal}; +use crate::{action_key, get_common_modifier, TO_NORMAL}; use crate::{ColoredElements, LinePart}; struct KeyShortcut { @@ -209,9 +209,9 @@ fn key_indicators( /// to get back to normal mode from any input mode, but they aren't of interest when searching /// for the super key. If for any input mode the user has bound only these keys to switching back /// to `InputMode::Normal`, a '?' will be displayed as keybinding instead. -pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<&Key> { +pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { mode_info - .keybinds + .get_mode_keybinds() .iter() .filter_map(|(key, vac)| match vac.first() { // No actions defined, ignore @@ -231,13 +231,13 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<&Key> { | InputMode::Tab | InputMode::Resize | InputMode::Move - | InputMode::Scroll - | InputMode::Session => Some(key), + | InputMode::Search + | InputMode::Session => Some(*key), _ => None, }; } if let actions::Action::Quit = vac { - return Some(key); + return Some(*key); } // Not a `SwitchToMode` or `Quit` action, ignore None @@ -248,7 +248,7 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<&Key> { pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) -> LinePart { // Find a common modifier if any - let prefix_text = match get_common_modifier(mode_switch_keys(mode_info)) { + let prefix_text = match get_common_modifier(mode_switch_keys(mode_info).iter().collect()) { Some(text) => format!(" {} +", text), _ => return LinePart::default(), }; @@ -273,7 +273,7 @@ pub fn to_char(kv: Vec) -> Option { .next(); // Maybe the user bound one of the ignored keys? if key.is_none() { - return kv.get(0).cloned(); + return kv.first().cloned(); } key.cloned() } @@ -281,48 +281,48 @@ pub fn to_char(kv: Vec) -> Option { pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { let supports_arrow_fonts = !help.capabilities.arrow_fonts; let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); - let binds = &help.keybinds; + let binds = &help.get_mode_keybinds(); // Unselect all by default let mut default_keys = vec![ KeyShortcut::new( KeyMode::Unselected, KeyAction::Lock, - to_char(action_key!(binds, Action::SwitchToMode(InputMode::Locked))), + to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Locked)])), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Pane, - to_char(action_key!(binds, Action::SwitchToMode(InputMode::Pane))), + to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Pane)])), ), KeyShortcut::new( KeyMode::Unselected, KeyAction::Tab, - to_char(action_key!(binds, Action::SwitchToMode(InputMode::Tab))), + to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Tab)])), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Resize, - to_char(action_key!(binds, Action::SwitchToMode(InputMode::Resize))), + to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Resize)])), ), KeyShortcut::new( KeyMode::Unselected, KeyAction::Move, - to_char(action_key!(binds, Action::SwitchToMode(InputMode::Move))), + to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Move)])), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Search, - to_char(action_key!(binds, Action::SwitchToMode(InputMode::Scroll))), + to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Search)])), ), KeyShortcut::new( KeyMode::Unselected, KeyAction::Session, - to_char(action_key!(binds, Action::SwitchToMode(InputMode::Session))), + to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Session)])), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Quit, - to_char(action_key!(binds, Action::Quit)), + to_char(action_key(&binds, &[Action::Quit])), ), ]; @@ -343,7 +343,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { }; if let Some(index) = mode_index { default_keys[index].mode = KeyMode::Selected; - default_keys[index].key = to_char(action_key!(binds, to_normal!())); + default_keys[index].key = to_char(action_key(&binds, &[TO_NORMAL])); } if help.mode == InputMode::Tmux { @@ -351,7 +351,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { default_keys.push(KeyShortcut::new( KeyMode::Selected, KeyAction::Tmux, - to_char(action_key!(binds, to_normal!())), + to_char(action_key(&binds, &[TO_NORMAL])), )); } diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index f32624ceaa..257f58cb30 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -1,4 +1,3 @@ -use super::{action_key, get_common_modifier, to_normal}; use ansi_term::{ ANSIStrings, Color::{Fixed, RGB}, @@ -9,8 +8,9 @@ use zellij_tile::prelude::*; use zellij_tile_utils::palette_match; use crate::{ + action_key, action_key_group, get_common_modifier, tip::{data::TIPS, TipFn}, - LinePart, MORE_MSG, + LinePart, MORE_MSG, TO_NORMAL, }; fn full_length_shortcut( @@ -59,7 +59,7 @@ fn full_length_shortcut( let key_separator = match &key_string[..] { "hjkl" => "", "←↓↑→" => "", - "←↓" => "", + "←→" => "", "↓↑" => "", _ => "|", }; @@ -149,12 +149,12 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { use actions::Direction as Dir; use actions::ResizeDirection as RDir; - let mut old_keymap = mi.keybinds.clone(); + let mut old_keymap = mi.get_mode_keybinds().clone(); let s = |string: &str| string.to_string(); // Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other // choices. Do it here before we dedupe the keymap below! - let to_normal_keys = action_key!(old_keymap, to_normal!()); + let to_normal_keys = action_key(&old_keymap, &[TO_NORMAL]); let to_normal_key = if to_normal_keys.contains(&Key::Char('\n')) { vec![Key::Char('\n')] } else { @@ -169,7 +169,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { let mut known_actions: Vec> = vec![]; let mut km = vec![]; - for (key, acvec) in old_keymap.into_iter() { + for (key, acvec) in old_keymap { if known_actions.contains(&acvec) { // This action is known already continue; @@ -180,69 +180,90 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { } if mi.mode == IM::Pane { vec![ - (s("Move focus"), s("Move"), action_key!(km, A::MoveFocus(_))), - (s("New"), s("New"), action_key!(km, A::NewPane(None), to_normal!())), - (s("Close"), s("Close"), action_key!(km, A::CloseFocus, to_normal!())), - (s("Rename"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenamePane), A::PaneNameInput(_))), - (s("Split down"), s("Down"), action_key!(km, A::NewPane(Some(Dir::Down)), to_normal!())), - (s("Split right"), s("Right"), action_key!(km, A::NewPane(Some(Dir::Right)), to_normal!())), - (s("Fullscreen"), s("Fullscreen"), action_key!(km, A::ToggleFocusFullscreen, to_normal!())), - (s("Frames"), s("Frames"), action_key!(km, A::TogglePaneFrames, to_normal!())), - (s("Floating toggle"), s("Floating"), action_key!(km, A::ToggleFloatingPanes, to_normal!())), - (s("Embed pane"), s("Embed"), action_key!(km, A::TogglePaneEmbedOrFloating, to_normal!())), - (s("Next"), s("Next"), action_key!(km, A::SwitchFocus)), + (s("Move focus"), s("Move"), + action_key_group(&km, &[&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)], + &[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])), + (s("New"), s("New"), action_key(&km, &[A::NewPane(None), TO_NORMAL])), + (s("Close"), s("Close"), action_key(&km, &[A::CloseFocus, TO_NORMAL])), + (s("Rename"), s("Rename"), + action_key(&km, &[A::SwitchToMode(IM::RenamePane), A::PaneNameInput(vec![0])])), + (s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down)), TO_NORMAL])), + (s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right)), TO_NORMAL])), + (s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])), + (s("Frames"), s("Frames"), action_key(&km, &[A::TogglePaneFrames, TO_NORMAL])), + (s("Floating toggle"), s("Floating"), + action_key(&km, &[A::ToggleFloatingPanes, TO_NORMAL])), + (s("Embed pane"), s("Embed"), action_key(&km, &[A::TogglePaneEmbedOrFloating, TO_NORMAL])), + (s("Next"), s("Next"), action_key(&km, &[A::SwitchFocus])), (s("Select pane"), s("Select"), to_normal_key), - ]} else if mi.mode == IM::Tab { vec![ - (s("Move focus"), s("Move"), action_key!(km, A::GoToPreviousTab).into_iter() - .chain(action_key!(km, A::GoToNextTab).into_iter()).collect()), - (s("New"), s("New"), action_key!(km, A::NewTab(None), to_normal!())), - (s("Close"), s("Close"), action_key!(km, A::CloseTab, to_normal!())), - (s("Rename"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenameTab), A::TabNameInput(_))), - (s("Sync"), s("Sync"), action_key!(km, A::ToggleActiveSyncTab, to_normal!())), - (s("Toggle"), s("Toggle"), action_key!(km, A::ToggleTab)), + ]} else if mi.mode == IM::Tab { + // With the default bindings, "Move focus" for tabs is tricky: It binds all the arrow keys + // to moving tabs focus (left/up go left, right/down go right). Since we sort the keys + // above and then dedpulicate based on the actions, we will end up with LeftArrow for + // "left" and DownArrow for "right". What we really expect is to see LeftArrow and + // RightArrow. + // FIXME: So for lack of a better idea we just check this case manually here. + let old_keymap = mi.get_mode_keybinds().clone(); + let focus_keys_full: Vec = action_key_group(&old_keymap, + &[&[A::GoToPreviousTab], &[A::GoToNextTab]]); + let focus_keys = if focus_keys_full.contains(&Key::Left) + && focus_keys_full.contains(&Key::Right) { + vec![Key::Left, Key::Right] + } else { + action_key_group(&km, &[&[A::GoToPreviousTab], &[A::GoToNextTab]]) + }; + + vec![ + (s("Move focus"), s("Move"), focus_keys), + (s("New"), s("New"), action_key(&km, &[A::NewTab(None), TO_NORMAL])), + (s("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])), + (s("Rename"), s("Rename"), action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), + (s("Sync"), s("Sync"), action_key(&km, &[A::ToggleActiveSyncTab, TO_NORMAL])), + (s("Toggle"), s("Toggle"), action_key(&km, &[A::ToggleTab])), (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Resize { vec![ - (s("Resize"), s("Resize"), action_key!(km, A::Resize(RDir::Left)).into_iter() - .chain(action_key!(km, A::Resize(RDir::Down)).into_iter()) - .chain(action_key!(km, A::Resize(RDir::Up)).into_iter()) - .chain(action_key!(km, A::Resize(RDir::Right)).into_iter()) - .collect::>()), + (s("Resize"), s("Resize"), action_key_group(&km, &[ + &[A::Resize(RDir::Left)], &[A::Resize(RDir::Down)], + &[A::Resize(RDir::Up)], &[A::Resize(RDir::Right)]])), (s("Increase/Decrease size"), s("Increase/Decrease"), - action_key!(km, A::Resize(RDir::Increase)).into_iter() - .chain(action_key!(km, A::Resize(RDir::Decrease)).into_iter()).collect()), + action_key_group(&km, &[&[A::Resize(RDir::Increase)], &[A::Resize(RDir::Decrease)]])), (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Move { vec![ - (s("Move"), s("Move"), action_key!(km, Action::MovePane(Some(_)))), - (s("Next pane"), s("Next"), action_key!(km, Action::MovePane(None))), + (s("Move"), s("Move"), action_key_group(&km, &[ + &[Action::MovePane(Some(Dir::Left))], &[Action::MovePane(Some(Dir::Down))], + &[Action::MovePane(Some(Dir::Up))], &[Action::MovePane(Some(Dir::Right))]])), + (s("Next pane"), s("Next"), action_key(&km, &[Action::MovePane(None)])), ]} else if mi.mode == IM::Scroll { vec![ - (s("Scroll"), s("Scroll"), action_key!(km, Action::ScrollDown).into_iter() - .chain(action_key!(km, Action::ScrollUp).into_iter()).collect()), - (s("Scroll page"), s("Scroll"), action_key!(km, Action::PageScrollDown).into_iter() - .chain(action_key!(km, Action::PageScrollUp).into_iter()).collect()), - (s("Scroll half page"), s("Scroll"), action_key!(km, Action::HalfPageScrollDown).into_iter() - .chain(action_key!(km, Action::HalfPageScrollUp).into_iter()).collect()), + (s("Scroll"), s("Scroll"), + action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])), + (s("Scroll page"), s("Scroll"), + action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])), + (s("Scroll half page"), s("Scroll"), + action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])), (s("Edit scrollback in default editor"), s("Edit"), - action_key!(km, Action::EditScrollback, to_normal!())), + action_key(&km, &[Action::EditScrollback, TO_NORMAL])), (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Session { vec![ - (s("Detach"), s("Detach"), action_key!(km, Action::Detach)), + (s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])), (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Tmux { vec![ - (s("Move focus"), s("Move"), action_key!(km, A::MoveFocus(_))), - (s("Split down"), s("Down"), action_key!(km, A::NewPane(Some(Dir::Down)), to_normal!())), - (s("Split right"), s("Right"), action_key!(km, A::NewPane(Some(Dir::Right)), to_normal!())), - (s("Fullscreen"), s("Fullscreen"), action_key!(km, A::ToggleFocusFullscreen, to_normal!())), - (s("New tab"), s("New"), action_key!(km, A::NewTab(None), to_normal!())), - (s("Rename tab"), s("Rename"), action_key!(km, A::SwitchToMode(IM::RenameTab), A::TabNameInput(_))), - (s("Previous Tab"), s("Previous"), action_key!(km, A::GoToPreviousTab, to_normal!())), - (s("Next Tab"), s("Next"), action_key!(km, A::GoToNextTab, to_normal!())), + (s("Move focus"), s("Move"), action_key_group(&km, &[ + &[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)], + &[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])), + (s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down)), TO_NORMAL])), + (s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right)), TO_NORMAL])), + (s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])), + (s("New tab"), s("New"), action_key(&km, &[A::NewTab(None), TO_NORMAL])), + (s("Rename tab"), s("Rename"), + action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), + (s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])), + (s("Next Tab"), s("Next"), action_key(&km, &[A::GoToNextTab, TO_NORMAL])), (s("Select pane"), s("Select"), to_normal_key), ]} else if matches!(mi.mode, IM::RenamePane | IM::RenameTab) { vec![ (s("When done"), s("Done"), to_normal_key), - (s("Select pane"), s("Select"), action_key!(km, A::MoveFocusOrTab(Dir::Left)).into_iter() - .chain(action_key!(km, A::MoveFocus(Dir::Down)).into_iter()) - .chain(action_key!(km, A::MoveFocus(Dir::Up)).into_iter()) - .chain(action_key!(km, A::MoveFocusOrTab(Dir::Right)).into_iter()).collect()), + (s("Select pane"), s("Select"), action_key_group(&km, &[ + &[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)], + &[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])), ]} else { vec![] } } @@ -279,7 +300,6 @@ fn best_effort_shortcut_list_nonstandard_mode(help: &ModeInfo, max_len: usize) - for (_, short, keys) in keys_and_hints.into_iter() { let new_line_part = add_shortcut(help, &line_part, &short, keys.to_vec()); if new_line_part.len + MORE_MSG.chars().count() > max_len { - // TODO: better line_part.part = format!("{}{}", line_part.part, MORE_MSG); line_part.len += MORE_MSG.chars().count(); break; @@ -387,7 +407,7 @@ pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> Line pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { let palette = mode_info.style.colors; - let km = &mode_info.keybinds; + let km = &mode_info.get_mode_keybinds(); let white_color = match palette.white { PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), PaletteColor::EightBit(color) => Fixed(color), @@ -406,7 +426,7 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { let press = "Press "; let pane_mode = format!( "{}", - action_key!(km, Action::SwitchToMode(InputMode::Pane)) + action_key(km, &[Action::SwitchToMode(InputMode::Pane)]) .first() .unwrap_or(&Key::Char('?')) ); From 9190c2734feb3911ab75d58d1617ea1f55a995b2 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 13:37:24 +0200 Subject: [PATCH 64/90] status_bar/second_line: Fix floating pane hint that tells the user what keybinding to press to suppress the currently active floating panes. This was previously hardcoded. --- default-plugins/status-bar/src/second_line.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 257f58cb30..6824fc30c0 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -432,9 +432,15 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { ); let plus = ", "; let p_left_separator = "<"; - // FIXME: This is wrong. We cannot know this from normal mode, because we transfer only the - // keybindings for the current InputMode. - let p = "w"; + let p = format!( + "{}", + action_key( + &mode_info.get_keybinds_for_mode(InputMode::Pane), + &[Action::ToggleFloatingPanes, TO_NORMAL] + ) + .first() + .unwrap_or(&Key::Char('?')) + ); let p_right_separator = "> "; let to_hide = "to hide."; From 20a60db38ab32196187266eedae116f131292f47 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 13:38:14 +0200 Subject: [PATCH 65/90] utils: Send full keybinds in `ModeInfo` instead of the currently active `ModeKeybinds` for the active input mode. Some of the UI issues cannot be solved without having access to *all* keybindings. --- zellij-utils/src/data.rs | 22 +++++++++++++++++++--- zellij-utils/src/input/keybinds.rs | 8 ++++++++ zellij-utils/src/input/mod.rs | 2 +- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index c3f996aa2d..287adc769f 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -61,7 +61,7 @@ pub enum Key { impl fmt::Display for Key { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Key::Backspace => write!(f, "Backspace"), + Key::Backspace => write!(f, "BACKSPACE"), Key::Left => write!(f, "{}", Direction::Left), Key::Right => write!(f, "{}", Direction::Right), Key::Up => write!(f, "{}", Direction::Up), @@ -83,7 +83,7 @@ impl fmt::Display for Key { Key::Alt(c) => write!(f, "Alt+{}", c), Key::Ctrl(c) => write!(f, "Ctrl+{}", Key::Char(*c)), Key::Null => write!(f, "NULL"), - Key::Esc => write!(f, "Esc"), + Key::Esc => write!(f, "ESC"), } } } @@ -299,12 +299,28 @@ pub struct Style { #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ModeInfo { pub mode: InputMode, - pub keybinds: Vec<(Key, Vec)>, + // FIXME: Poor devs hashtable since HashTable can't derive `Default`... + pub keybinds: Vec<(InputMode, Vec<(Key, Vec)>)>, pub style: Style, pub capabilities: PluginCapabilities, pub session_name: Option, } +impl ModeInfo { + pub fn get_mode_keybinds(&self) -> Vec<(Key, Vec)> { + self.get_keybinds_for_mode(self.mode) + } + + pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(Key, Vec)> { + for (vec_mode, map) in &self.keybinds { + if mode == *vec_mode { + return map.to_vec(); + } + } + vec![] + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct TabInfo { /* subset of fields to publish to plugins */ diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index c15310c403..29b346d8b9 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -84,6 +84,14 @@ impl Keybinds { .keybinds } + pub fn to_keybinds_vec(&self) -> Vec<(InputMode, Vec<(Key, Vec)>)> { + let mut ret = vec![]; + for (mode, mode_binds) in &self.0 { + ret.push((mode.clone(), mode_binds.to_cloned_vec())); + } + ret + } + pub fn get_mode_keybinds(&self, mode: &InputMode) -> &ModeKeybinds { self.0 .get(mode) diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 1166a3d9a0..29a7816cd6 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -31,7 +31,7 @@ mod not_wasm { attributes: &ClientAttributes, capabilities: PluginCapabilities, ) -> ModeInfo { - let keybinds = attributes.keybinds.get_mode_keybinds(&mode).to_cloned_vec(); + let keybinds = attributes.keybinds.to_keybinds_vec(); let session_name = envs::get_session_name().ok(); ModeInfo { From b43802232b240c7b558dbfc97df2445946496026 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 17:07:52 +0200 Subject: [PATCH 66/90] utils: Refactor keybinds vec into type to make clippy happy. --- zellij-utils/src/data.rs | 6 ++++-- zellij-utils/src/input/keybinds.rs | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 287adc769f..b28972920c 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -293,14 +293,16 @@ pub struct Style { pub rounded_corners: bool, } +// FIXME: Poor devs hashtable since HashTable can't derive `Default`... +pub type KeybindsVec = Vec<(InputMode, Vec<(Key, Vec)>)>; + /// Represents the contents of the help message that is printed in the status bar, /// which indicates the current [`InputMode`] and what the keybinds for that mode /// are. Related to the default `status-bar` plugin. #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ModeInfo { pub mode: InputMode, - // FIXME: Poor devs hashtable since HashTable can't derive `Default`... - pub keybinds: Vec<(InputMode, Vec<(Key, Vec)>)>, + pub keybinds: KeybindsVec, pub style: Style, pub capabilities: PluginCapabilities, pub session_name: Option, diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index 29b346d8b9..6e6f2ab99b 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap}; use super::actions::Action; use super::config; -use crate::data::{InputMode, Key}; +use crate::data::{InputMode, Key, KeybindsVec}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; @@ -84,10 +84,10 @@ impl Keybinds { .keybinds } - pub fn to_keybinds_vec(&self) -> Vec<(InputMode, Vec<(Key, Vec)>)> { + pub fn to_keybinds_vec(&self) -> KeybindsVec { let mut ret = vec![]; for (mode, mode_binds) in &self.0 { - ret.push((mode.clone(), mode_binds.to_cloned_vec())); + ret.push((*mode, mode_binds.to_cloned_vec())); } ret } From 68a1326f47375e51837ea81598dd4a9c21a249b2 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 17:08:31 +0200 Subject: [PATCH 67/90] status_bar/first_line: Remove needless borrows --- default-plugins/status-bar/src/first_line.rs | 34 +++++++++++++------- default-plugins/status-bar/src/main.rs | 7 ++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 196464405e..7ba43177f2 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -231,7 +231,7 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { | InputMode::Tab | InputMode::Resize | InputMode::Move - | InputMode::Search + | InputMode::Scroll | InputMode::Session => Some(*key), _ => None, }; @@ -287,42 +287,54 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { KeyShortcut::new( KeyMode::Unselected, KeyAction::Lock, - to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Locked)])), + to_char(action_key( + binds, + &[Action::SwitchToMode(InputMode::Locked)], + )), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Pane, - to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Pane)])), + to_char(action_key(binds, &[Action::SwitchToMode(InputMode::Pane)])), ), KeyShortcut::new( KeyMode::Unselected, KeyAction::Tab, - to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Tab)])), + to_char(action_key(binds, &[Action::SwitchToMode(InputMode::Tab)])), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Resize, - to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Resize)])), + to_char(action_key( + binds, + &[Action::SwitchToMode(InputMode::Resize)], + )), ), KeyShortcut::new( KeyMode::Unselected, KeyAction::Move, - to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Move)])), + to_char(action_key(binds, &[Action::SwitchToMode(InputMode::Move)])), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Search, - to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Search)])), + to_char(action_key( + binds, + &[Action::SwitchToMode(InputMode::Scroll)], + )), ), KeyShortcut::new( KeyMode::Unselected, KeyAction::Session, - to_char(action_key(&binds, &[Action::SwitchToMode(InputMode::Session)])), + to_char(action_key( + binds, + &[Action::SwitchToMode(InputMode::Session)], + )), ), KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Quit, - to_char(action_key(&binds, &[Action::Quit])), + to_char(action_key(binds, &[Action::Quit])), ), ]; @@ -343,7 +355,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { }; if let Some(index) = mode_index { default_keys[index].mode = KeyMode::Selected; - default_keys[index].key = to_char(action_key(&binds, &[TO_NORMAL])); + default_keys[index].key = to_char(action_key(binds, &[TO_NORMAL])); } if help.mode == InputMode::Tmux { @@ -351,7 +363,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { default_keys.push(KeyShortcut::new( KeyMode::Selected, KeyAction::Tmux, - to_char(action_key(&binds, &[TO_NORMAL])), + to_char(action_key(binds, &[TO_NORMAL])), )); } diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 59889fabbc..c2f9443d19 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -5,8 +5,8 @@ mod tip; use ansi_term::Style; use std::fmt::{Display, Error, Formatter}; -use zellij_tile::prelude::*; use zellij_tile::prelude::actions::Action; +use zellij_tile::prelude::*; use zellij_tile_utils::style; use first_line::ctrl_keys; @@ -302,8 +302,9 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { // TODO: Accept multiple sequences of patterns, possible separated by '|', and bin them together // into one group under 'text'. pub fn action_key(keymap: &Vec<(Key, Vec)>, action: &[Action]) -> Vec { - keymap.iter(). - filter_map(|(key, acvec)| { + keymap + .iter() + .filter_map(|(key, acvec)| { if acvec.as_slice() == action { Some(*key) } else { From 332596fe9ddae714da136e88eb60e31b3f5fa3bc Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 17:09:26 +0200 Subject: [PATCH 68/90] status_bar: Factor out printing keybindings into a separate function that takes a vector of keys and a palette and returns the painted key groups, with correct inter-character separation where necessary and factoring out common modifier keys. --- default-plugins/status-bar/src/main.rs | 82 +++++++++++++++++-- default-plugins/status-bar/src/second_line.rs | 69 ++++------------ 2 files changed, 91 insertions(+), 60 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index c2f9443d19..aa21db1dd3 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -2,12 +2,16 @@ mod first_line; mod second_line; mod tip; -use ansi_term::Style; +use ansi_term::{ + ANSIString, + Colour::{Fixed, RGB}, + Style, +}; use std::fmt::{Display, Error, Formatter}; use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; -use zellij_tile_utils::style; +use zellij_tile_utils::{palette_match, style}; use first_line::ctrl_keys; use second_line::{ @@ -20,6 +24,8 @@ 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 = ""; static MORE_MSG: &str = " ... "; +/// Shorthand for `Action::SwitchToMode(InputMode::Normal)`. +const TO_NORMAL: Action = Action::SwitchToMode(InputMode::Normal); #[derive(Default)] struct State { @@ -301,7 +307,7 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { /// trigger the action pattern are returned as vector of `Vec`. // TODO: Accept multiple sequences of patterns, possible separated by '|', and bin them together // into one group under 'text'. -pub fn action_key(keymap: &Vec<(Key, Vec)>, action: &[Action]) -> Vec { +pub fn action_key(keymap: &[(Key, Vec)], action: &[Action]) -> Vec { keymap .iter() .filter_map(|(key, acvec)| { @@ -314,13 +320,75 @@ pub fn action_key(keymap: &Vec<(Key, Vec)>, action: &[Action]) -> Vec>() } -pub fn action_key_group(keymap: &Vec<(Key, Vec)>, actions: &[&[Action]]) -> Vec { +pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> Vec { let mut ret = vec![]; for action in actions { - ret.extend(action_key(&keymap, action)); + ret.extend(action_key(keymap, action)); } ret } -/// Shorthand for `Action::SwitchToMode(InputMode::Normal)`. -pub const TO_NORMAL: Action = Action::SwitchToMode(InputMode::Normal); +pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec> { + let text_color = palette_match!(match palette.theme_hue { + ThemeHue::Dark => palette.white, + ThemeHue::Light => palette.black, + }); + let green_color = palette_match!(palette.green); + let orange_color = palette_match!(palette.orange); + let mut ret = vec![]; + + // Prints modifier key + let modifier_str = match get_common_modifier(keyvec.iter().collect()) { + Some(modifier) => modifier, + None => "".to_string(), + }; + let no_modifier = modifier_str.is_empty(); + let painted_modifier = if modifier_str.is_empty() { + Style::new().paint("") + } else { + Style::new().fg(orange_color).bold().paint(modifier_str) + }; + ret.push(painted_modifier); + + // Prints key group start + let group_start_str = if no_modifier { "<" } else { " + <" }; + ret.push(Style::new().fg(text_color).paint(group_start_str)); + + // Prints the keys + let key = keyvec + .iter() + .map(|key| { + if no_modifier { + format!("{}", key) + } else { + match key { + Key::Ctrl(c) => format!("{}", c), + Key::Alt(c) => format!("{}", c), + _ => format!("{}", key), + } + } + }) + .collect::>(); + + // Special handling of some pre-defined keygroups + let key_string = key.join(""); + let key_separator = match &key_string[..] { + "hjkl" => "", + "←↓↑→" => "", + "←→" => "", + "↓↑" => "", + _ => "|", + }; + + for (idx, key) in key.iter().enumerate() { + if idx > 0 && !key_separator.is_empty() { + ret.push(Style::new().fg(text_color).paint(key_separator)); + } + ret.push(Style::new().fg(green_color).bold().paint(key.clone())); + } + + let group_end_str = ">"; + ret.push(Style::new().fg(text_color).paint(group_end_str)); + + ret +} diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 6824fc30c0..2586b63b79 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -1,5 +1,5 @@ use ansi_term::{ - ANSIStrings, + unstyled_len, ANSIString, ANSIStrings, Color::{Fixed, RGB}, Style, }; @@ -8,7 +8,7 @@ use zellij_tile::prelude::*; use zellij_tile_utils::palette_match; use crate::{ - action_key, action_key_group, get_common_modifier, + action_key, action_key_group, style_key_with_modifier, tip::{data::TIPS, TipFn}, LinePart, MORE_MSG, TO_NORMAL, }; @@ -22,63 +22,26 @@ fn full_length_shortcut( if key.is_empty() { return LinePart::default(); } + let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, ThemeHue::Light => palette.black, }); - let modifier = match get_common_modifier(key.iter().collect()) { - Some(text) => format!("{} + ", text), - None => String::from(""), - }; - let key = key - .iter() - .map(|key| { - if modifier.is_empty() { - format!("{}", key) - } else { - match key { - Key::Ctrl(c) => format!("{}", c), - Key::Alt(c) => format!("{}", c), - _ => format!("{}", key), - } - } - }) - .collect::>(); - - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); let separator = if is_first_shortcut { " " } else { " / " }; + let mut bits: Vec = vec![Style::new().fg(text_color).paint(separator)]; + bits.extend(style_key_with_modifier(&key, &palette)); + bits.push( + Style::new() + .fg(text_color) + .bold() + .paint(format!(" {}", action)), + ); + let part = ANSIStrings(&bits); - let mut ansi_string = vec![ - Style::new().fg(text_color).paint(separator), - Style::new().fg(orange_color).bold().paint(&modifier), - Style::new().fg(text_color).paint("<"), - ]; - let key_string = key.join(""); - let key_separator = match &key_string[..] { - "hjkl" => "", - "←↓↑→" => "", - "←→" => "", - "↓↑" => "", - _ => "|", - }; - for (idx, key) in key.iter().enumerate() { - if idx > 0 && !key_separator.is_empty() { - ansi_string.push(Style::new().fg(text_color).paint(key_separator)); - } - ansi_string.push(Style::new().fg(green_color).bold().paint(key)); - } - ansi_string.push(Style::new().fg(text_color).paint("> ")); - ansi_string.push(Style::new().fg(text_color).bold().paint(action)); LinePart { - part: ANSIStrings(&ansi_string).to_string(), - len: separator.chars().count() // " " or " / " - + modifier.chars().count() // Modifier (Ctrl, Alt), if any - + 1 // "<" - + key.join(key_separator).chars().count() // The key shortcut - + 2 // "> " - + action.chars().count(), // The action associated with the key + part: part.to_string(), + len: unstyled_len(&part), } } @@ -149,7 +112,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { use actions::Direction as Dir; use actions::ResizeDirection as RDir; - let mut old_keymap = mi.get_mode_keybinds().clone(); + let mut old_keymap = mi.get_mode_keybinds(); let s = |string: &str| string.to_string(); // Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other @@ -203,7 +166,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { // "left" and DownArrow for "right". What we really expect is to see LeftArrow and // RightArrow. // FIXME: So for lack of a better idea we just check this case manually here. - let old_keymap = mi.get_mode_keybinds().clone(); + let old_keymap = mi.get_mode_keybinds(); let focus_keys_full: Vec = action_key_group(&old_keymap, &[&[A::GoToPreviousTab], &[A::GoToNextTab]]); let focus_keys = if focus_keys_full.contains(&Key::Left) From 6a3f2ad39579d54ca5de032412405a2cf43a864d Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 14 Jul 2022 17:10:50 +0200 Subject: [PATCH 69/90] status_bar/tip: Use real keybindings instead of printing hard-coded messages to the user. --- default-plugins/status-bar/src/second_line.rs | 6 +- .../status-bar/src/tip/data/compact_layout.rs | 70 ++++----- .../src/tip/data/edit_scrollbuffer.rs | 74 +++++----- .../src/tip/data/floating_panes_mouse.rs | 20 +-- .../tip/data/move_focus_hjkl_tab_switch.rs | 79 ++++++----- .../status-bar/src/tip/data/quicknav.rs | 134 +++++++++--------- .../tip/data/send_mouse_click_to_terminal.rs | 20 +-- .../status-bar/src/tip/data/sync_tab.rs | 79 +++++------ .../status-bar/src/tip/data/use_mouse.rs | 14 +- .../src/tip/data/zellij_setup_check.rs | 14 +- default-plugins/status-bar/src/tip/mod.rs | 3 +- 11 files changed, 266 insertions(+), 247 deletions(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 2586b63b79..23ce69c177 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -232,7 +232,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { - InputMode::Normal => tip(help.style.colors), + InputMode::Normal => tip(help), InputMode::Locked => locked_interface_indication(help.style.colors), _ => full_shortcut_list_nonstandard_mode(help), } @@ -250,7 +250,7 @@ fn shortened_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart { fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { - InputMode::Normal => tip(help.style.colors), + InputMode::Normal => tip(help), InputMode::Locked => locked_interface_indication(help.style.colors), _ => shortened_shortcut_list_nonstandard_mode(help), } @@ -275,7 +275,7 @@ fn best_effort_shortcut_list_nonstandard_mode(help: &ModeInfo, max_len: usize) - fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> LinePart { match help.mode { InputMode::Normal => { - let line_part = tip(help.style.colors); + let line_part = tip(help); if line_part.len <= max_len { line_part } else { diff --git a/default-plugins/status-bar/src/tip/data/compact_layout.rs b/default-plugins/status-bar/src/tip/data/compact_layout.rs index 1632a836ee..43249f3725 100644 --- a/default-plugins/status-bar/src/tip/data/compact_layout.rs +++ b/default-plugins/status-bar/src/tip/data/compact_layout.rs @@ -5,12 +5,13 @@ use ansi_term::{ }; use crate::LinePart; -use zellij_tile::prelude::*; +use crate::{action_key, style_key_with_modifier}; +use zellij_tile::prelude::{actions::Action, *}; use zellij_tile_utils::palette_match; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,13 +22,12 @@ macro_rules! strings { }}; } -pub fn compact_layout_full(palette: Palette) -> LinePart { +pub fn compact_layout_full(help: &ModeInfo) -> LinePart { // Tip: UI taking up too much space? Start Zellij with // zellij -l compact or remove pane frames with Ctrl +

+ - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("UI taking up too much space? Start Zellij with "), Style::new() @@ -35,21 +35,17 @@ pub fn compact_layout_full(palette: Palette) -> LinePart { .bold() .paint("zellij -l compact"), Style::new().paint(" or remove pane frames with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("

"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) } -pub fn compact_layout_medium(palette: Palette) -> LinePart { +pub fn compact_layout_medium(help: &ModeInfo) -> LinePart { // Tip: To save screen space, start Zellij with // zellij -l compact or remove pane frames with Ctrl +

+ - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("To save screen space, start Zellij with "), Style::new() @@ -57,31 +53,41 @@ pub fn compact_layout_medium(palette: Palette) -> LinePart { .bold() .paint("zellij -l compact"), Style::new().paint(" or remove frames with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("

"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) } -pub fn compact_layout_short(palette: Palette) -> LinePart { +pub fn compact_layout_short(help: &ModeInfo) -> LinePart { // Save screen space, start Zellij with // zellij -l compact or remove pane frames with Ctrl +

+ - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Save screen space, start with: "), Style::new() .fg(green_color) .bold() .paint("zellij -l compact"), Style::new().paint(" or remove frames with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("

"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) +} + +fn add_keybinds(help: &ModeInfo) -> Vec { + let to_pane = action_key( + &help.get_mode_keybinds(), + &[Action::SwitchToMode(InputMode::Pane)], + ); + let pane_frames = action_key( + &help.get_keybinds_for_mode(InputMode::Pane), + &[Action::TogglePaneFrames], + ); + + let mut bits = vec![]; + bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); + bits.push(Style::new().paint(", ")); + bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); + bits } diff --git a/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs b/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs index 3807f55db2..d501405932 100644 --- a/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs +++ b/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs @@ -4,13 +4,13 @@ use ansi_term::{ Style, }; -use crate::LinePart; -use zellij_tile::prelude::*; +use crate::{action_key, style_key_with_modifier, LinePart}; +use zellij_tile::prelude::{actions::Action, *}; use zellij_tile_utils::palette_match; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,58 +21,66 @@ macro_rules! strings { }}; } -pub fn edit_scrollbuffer_full(palette: Palette) -> LinePart { +pub fn edit_scrollbuffer_full(help: &ModeInfo) -> LinePart { // Tip: Search through the scrollbuffer using your default $EDITOR with // Ctrl + + - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("Search through the scrollbuffer using your default "), Style::new().fg(green_color).bold().paint("$EDITOR"), Style::new().paint(" with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) } -pub fn edit_scrollbuffer_medium(palette: Palette) -> LinePart { +pub fn edit_scrollbuffer_medium(help: &ModeInfo) -> LinePart { // Tip: Search the scrollbuffer using your $EDITOR with // Ctrl + + - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("Search the scrollbuffer using your "), Style::new().fg(green_color).bold().paint("$EDITOR"), Style::new().paint(" with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) } -pub fn edit_scrollbuffer_short(palette: Palette) -> LinePart { +pub fn edit_scrollbuffer_short(help: &ModeInfo) -> LinePart { // Search using $EDITOR with // Ctrl + + - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Search using "), Style::new().fg(green_color).bold().paint("$EDITOR"), Style::new().paint(" with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) +} + +fn add_keybinds(help: &ModeInfo) -> Vec { + let to_pane = action_key( + &help.get_mode_keybinds(), + &[Action::SwitchToMode(InputMode::Scroll)], + ); + let pane_frames = action_key( + &help.get_keybinds_for_mode(InputMode::Pane), + &[ + Action::EditScrollback, + Action::SwitchToMode(InputMode::Normal), + ], + ); + + let mut bits = vec![]; + bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); + bits.push(Style::new().paint(", ")); + bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); + bits } diff --git a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs index 64594987b0..819c992b9e 100644 --- a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs +++ b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs @@ -10,7 +10,7 @@ use zellij_tile_utils::palette_match; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,10 +21,10 @@ macro_rules! strings { }}; } -pub fn floating_panes_mouse_full(palette: Palette) -> LinePart { +pub fn floating_panes_mouse_full(help: &ModeInfo) -> LinePart { // Tip: Toggle floating panes with Ctrl +

+ and move them with keyboard or mouse - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), @@ -38,10 +38,10 @@ pub fn floating_panes_mouse_full(palette: Palette) -> LinePart { ]) } -pub fn floating_panes_mouse_medium(palette: Palette) -> LinePart { +pub fn floating_panes_mouse_medium(help: &ModeInfo) -> LinePart { // Tip: Toggle floating panes with Ctrl +

+ - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), Style::new().paint("Toggle floating panes with "), @@ -53,10 +53,10 @@ pub fn floating_panes_mouse_medium(palette: Palette) -> LinePart { ]) } -pub fn floating_panes_mouse_short(palette: Palette) -> LinePart { +pub fn floating_panes_mouse_short(help: &ModeInfo) -> LinePart { // Ctrl +

+ => floating panes - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().fg(orange_color).bold().paint(" Ctrl"), diff --git a/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs b/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs index 8e9f678c6e..20292927e5 100644 --- a/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs +++ b/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs @@ -1,16 +1,14 @@ -use ansi_term::{ - unstyled_len, ANSIString, ANSIStrings, - Color::{Fixed, RGB}, - Style, -}; +use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style}; -use crate::LinePart; -use zellij_tile::prelude::*; -use zellij_tile_utils::palette_match; +use crate::{action_key, action_key_group, style_key_with_modifier, LinePart}; +use zellij_tile::prelude::{ + actions::{Action, Direction}, + *, +}; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,44 +19,51 @@ macro_rules! strings { }}; } -pub fn move_focus_hjkl_tab_switch_full(palette: Palette) -> LinePart { +pub fn move_focus_hjkl_tab_switch_full(help: &ModeInfo) -> LinePart { // Tip: When changing focus with Alt + <←↓↑→> moving off screen left/right focuses the next tab. - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); - - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("When changing focus with "), - Style::new().fg(orange_color).bold().paint("Alt"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("<←↓↑→>"), - Style::new().paint(" moving off screen left/right focuses the next tab."), - ]) + ]; + bits.extend(add_keybinds(help)); + bits.push(Style::new().paint(" moving off screen left/right focuses the next tab.")); + strings!(&bits) } -pub fn move_focus_hjkl_tab_switch_medium(palette: Palette) -> LinePart { +pub fn move_focus_hjkl_tab_switch_medium(help: &ModeInfo) -> LinePart { // Tip: Changing focus with Alt + <←↓↑→> off screen focuses the next tab. - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("Changing focus with "), - Style::new().fg(orange_color).bold().paint("Alt"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("<←↓↑→>"), - Style::new().paint(" off screen focuses the next tab."), - ]) + ]; + bits.extend(add_keybinds(help)); + bits.push(Style::new().paint(" off screen focuses the next tab.")); + strings!(&bits) } -pub fn move_focus_hjkl_tab_switch_short(palette: Palette) -> LinePart { +pub fn move_focus_hjkl_tab_switch_short(help: &ModeInfo) -> LinePart { // Alt + <←↓↑→> off screen edge focuses next tab. - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let mut bits = add_keybinds(help); + bits.push(Style::new().paint(" off screen edge focuses next tab.")); + strings!(&bits) +} + +fn add_keybinds(help: &ModeInfo) -> Vec { + let to_pane = action_key( + &help.get_mode_keybinds(), + &[Action::SwitchToMode(InputMode::Pane)], + ); + let pane_frames = action_key_group( + &help.get_keybinds_for_mode(InputMode::Normal), + &[ + &[Action::MoveFocus(Direction::Left)], + &[Action::MoveFocus(Direction::Down)], + ], + ); - strings!(&[ - Style::new().fg(orange_color).bold().paint(" Alt"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("<←↓↑→>"), - Style::new().paint(" off screen edge focuses next tab."), - ]) + let mut bits = vec![]; + bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); + bits.push(Style::new().paint(", ")); + bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); + bits } diff --git a/default-plugins/status-bar/src/tip/data/quicknav.rs b/default-plugins/status-bar/src/tip/data/quicknav.rs index 21d46fe8ff..e256e243c4 100644 --- a/default-plugins/status-bar/src/tip/data/quicknav.rs +++ b/default-plugins/status-bar/src/tip/data/quicknav.rs @@ -1,16 +1,14 @@ -use ansi_term::{ - unstyled_len, ANSIString, ANSIStrings, - Color::{Fixed, RGB}, - Style, -}; +use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style}; -use crate::LinePart; -use zellij_tile::prelude::*; -use zellij_tile_utils::palette_match; +use crate::{action_key, action_key_group, style_key_with_modifier, LinePart}; +use zellij_tile::prelude::{ + actions::{Action, Direction, ResizeDirection}, + *, +}; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,66 +19,74 @@ macro_rules! strings { }}; } -pub fn quicknav_full(palette: Palette) -> LinePart { - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); +pub fn quicknav_full(help: &ModeInfo) -> LinePart { + let groups = add_keybinds(help); - strings!(&[ - Style::new().paint(" Tip: "), - Style::new().fg(orange_color).bold().paint("Alt"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - 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."), - ]) + let mut bits = vec![Style::new().paint(" Tip: ")]; + bits.extend(groups.new_pane); + bits.push(Style::new().paint(" => open new pane. ")); + bits.extend(groups.move_focus); + bits.push(Style::new().paint(" => navigate between panes. ")); + bits.extend(groups.resize); + bits.push(Style::new().paint(" => increase/decrease pane size.")); + strings!(&bits) } -pub fn quicknav_medium(palette: Palette) -> LinePart { - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); +pub fn quicknav_medium(help: &ModeInfo) -> LinePart { + let groups = add_keybinds(help); + + let mut bits = vec![Style::new().paint(" Tip: ")]; + bits.extend(groups.new_pane); + bits.push(Style::new().paint(" => new pane. ")); + bits.extend(groups.move_focus); + bits.push(Style::new().paint(" => navigate. ")); + bits.extend(groups.resize); + bits.push(Style::new().paint(" => resize pane.")); + strings!(&bits) +} + +pub fn quicknav_short(help: &ModeInfo) -> LinePart { + let groups = add_keybinds(help); + + let mut bits = vec![Style::new().paint(" QuickNav: ")]; + bits.extend(groups.new_pane); + bits.push(Style::new().paint(" / ")); + bits.extend(groups.move_focus); + bits.push(Style::new().paint(" / ")); + bits.extend(groups.resize); + bits.push(Style::new().paint(" / ")); + strings!(&bits) +} - strings!(&[ - Style::new().paint(" Tip: "), - Style::new().fg(orange_color).bold().paint("Alt"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - 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."), - ]) +struct Keygroups<'a> { + new_pane: Vec>, + move_focus: Vec>, + resize: Vec>, } -pub fn quicknav_short(palette: Palette) -> LinePart { - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); +fn add_keybinds(help: &ModeInfo) -> Keygroups { + let normal_keymap = help.get_keybinds_for_mode(InputMode::Normal); + let new_pane = action_key(&normal_keymap, &[Action::NewPane(None)]); + let move_focus = action_key_group( + &normal_keymap, + &[ + &[Action::MoveFocus(Direction::Left)], + &[Action::MoveFocus(Direction::Down)], + &[Action::MoveFocus(Direction::Up)], + &[Action::MoveFocus(Direction::Right)], + ], + ); + let resize = action_key_group( + &normal_keymap, + &[ + &[Action::Resize(ResizeDirection::Increase)], + &[Action::Resize(ResizeDirection::Decrease)], + ], + ); - 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("+->"), - ]) + Keygroups { + new_pane: style_key_with_modifier(&new_pane, &help.style.colors), + move_focus: style_key_with_modifier(&move_focus, &help.style.colors), + resize: style_key_with_modifier(&resize, &help.style.colors), + } } diff --git a/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs b/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs index a4f7ee60a8..4d94f7d0c0 100644 --- a/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs +++ b/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs @@ -10,7 +10,7 @@ use zellij_tile_utils::palette_match; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,10 +21,10 @@ macro_rules! strings { }}; } -pub fn mouse_click_to_terminal_full(palette: Palette) -> LinePart { +pub fn mouse_click_to_terminal_full(help: &ModeInfo) -> LinePart { // Tip: SHIFT + bypasses Zellij and sends the mouse click directly to the terminal - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), @@ -35,10 +35,10 @@ pub fn mouse_click_to_terminal_full(palette: Palette) -> LinePart { ]) } -pub fn mouse_click_to_terminal_medium(palette: Palette) -> LinePart { +pub fn mouse_click_to_terminal_medium(help: &ModeInfo) -> LinePart { // Tip: SHIFT + sends the click directly to the terminal - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), Style::new().fg(orange_color).bold().paint("SHIFT"), @@ -48,10 +48,10 @@ pub fn mouse_click_to_terminal_medium(palette: Palette) -> LinePart { ]) } -pub fn mouse_click_to_terminal_short(palette: Palette) -> LinePart { +pub fn mouse_click_to_terminal_short(help: &ModeInfo) -> LinePart { // Tip: SHIFT + => sends click to terminal. - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let green_color = palette_match!(help.style.colors.green); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), diff --git a/default-plugins/status-bar/src/tip/data/sync_tab.rs b/default-plugins/status-bar/src/tip/data/sync_tab.rs index 27d0ae7f06..5ab11a0832 100644 --- a/default-plugins/status-bar/src/tip/data/sync_tab.rs +++ b/default-plugins/status-bar/src/tip/data/sync_tab.rs @@ -1,16 +1,11 @@ -use ansi_term::{ - unstyled_len, ANSIString, ANSIStrings, - Color::{Fixed, RGB}, - Style, -}; +use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style}; -use crate::LinePart; -use zellij_tile::prelude::*; -use zellij_tile_utils::palette_match; +use crate::{action_key, style_key_with_modifier, LinePart}; +use zellij_tile::prelude::{actions::Action, *}; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,49 +16,49 @@ macro_rules! strings { }}; } -pub fn sync_tab_full(palette: Palette) -> LinePart { +pub fn sync_tab_full(help: &ModeInfo) -> LinePart { // Tip: Sync a tab and write keyboard input to all panes with Ctrl + + - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); - - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("Sync a tab and write keyboard input to all its panes with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) } -pub fn sync_tab_medium(palette: Palette) -> LinePart { +pub fn sync_tab_medium(help: &ModeInfo) -> LinePart { // Tip: Sync input to panes in a tab with Ctrl + + - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); - - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("Sync input to panes in a tab with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) } -pub fn sync_tab_short(palette: Palette) -> LinePart { +pub fn sync_tab_short(help: &ModeInfo) -> LinePart { // Sync input in a tab with Ctrl + + - let green_color = palette_match!(palette.green); - let orange_color = palette_match!(palette.orange); + let mut bits = vec![Style::new().paint(" Sync input in a tab with ")]; + bits.extend(add_keybinds(help)); + strings!(&bits) +} + +fn add_keybinds(help: &ModeInfo) -> Vec { + let to_tab = action_key( + &help.get_mode_keybinds(), + &[Action::SwitchToMode(InputMode::Tab)], + ); + let sync_tabs = action_key( + &help.get_keybinds_for_mode(InputMode::Tab), + &[ + Action::ToggleActiveSyncTab, + Action::SwitchToMode(InputMode::Normal), + ], + ); - strings!(&[ - Style::new().paint(" Sync input in a tab with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + let mut bits = vec![]; + bits.extend(style_key_with_modifier(&to_tab, &help.style.colors)); + bits.push(Style::new().paint(", ")); + bits.extend(style_key_with_modifier(&sync_tabs, &help.style.colors)); + bits } diff --git a/default-plugins/status-bar/src/tip/data/use_mouse.rs b/default-plugins/status-bar/src/tip/data/use_mouse.rs index b9c8de580f..4bbfbf948d 100644 --- a/default-plugins/status-bar/src/tip/data/use_mouse.rs +++ b/default-plugins/status-bar/src/tip/data/use_mouse.rs @@ -10,7 +10,7 @@ use zellij_tile_utils::palette_match; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,10 +21,10 @@ macro_rules! strings { }}; } -pub fn use_mouse_full(palette: Palette) -> LinePart { +pub fn use_mouse_full(help: &ModeInfo) -> LinePart { // Tip: Use the mouse to switch pane focus, scroll through the pane // scrollbuffer, switch or scroll through tabs - let green_color = palette_match!(palette.green); + let green_color = palette_match!(help.style.colors.green); strings!(&[ Style::new().paint(" Tip: "), @@ -33,10 +33,10 @@ pub fn use_mouse_full(palette: Palette) -> LinePart { ]) } -pub fn use_mouse_medium(palette: Palette) -> LinePart { +pub fn use_mouse_medium(help: &ModeInfo) -> LinePart { // Tip: Use the mouse to switch panes/tabs or scroll through the pane // scrollbuffer - let green_color = palette_match!(palette.green); + let green_color = palette_match!(help.style.colors.green); strings!(&[ Style::new().paint(" Tip: "), @@ -45,9 +45,9 @@ pub fn use_mouse_medium(palette: Palette) -> LinePart { ]) } -pub fn use_mouse_short(palette: Palette) -> LinePart { +pub fn use_mouse_short(help: &ModeInfo) -> LinePart { // Tip: Use the mouse to switch panes/tabs or scroll - let green_color = palette_match!(palette.green); + let green_color = palette_match!(help.style.colors.green); strings!(&[ Style::new().fg(green_color).bold().paint(" Use the mouse"), diff --git a/default-plugins/status-bar/src/tip/data/zellij_setup_check.rs b/default-plugins/status-bar/src/tip/data/zellij_setup_check.rs index c45deac205..3dd7b6d853 100644 --- a/default-plugins/status-bar/src/tip/data/zellij_setup_check.rs +++ b/default-plugins/status-bar/src/tip/data/zellij_setup_check.rs @@ -10,7 +10,7 @@ use zellij_tile_utils::palette_match; macro_rules! strings { ($ANSIStrings:expr) => {{ - let strings: &[ANSIString<'static>] = $ANSIStrings; + let strings: &[ANSIString] = $ANSIStrings; let ansi_strings = ANSIStrings(strings); @@ -21,9 +21,9 @@ macro_rules! strings { }}; } -pub fn zellij_setup_check_full(palette: Palette) -> LinePart { +pub fn zellij_setup_check_full(help: &ModeInfo) -> LinePart { // Tip: Having issues with Zellij? Try running "zellij setup --check" - let orange_color = palette_match!(palette.orange); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), @@ -35,9 +35,9 @@ pub fn zellij_setup_check_full(palette: Palette) -> LinePart { ]) } -pub fn zellij_setup_check_medium(palette: Palette) -> LinePart { +pub fn zellij_setup_check_medium(help: &ModeInfo) -> LinePart { // Tip: Run "zellij setup --check" to find issues - let orange_color = palette_match!(palette.orange); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), @@ -50,9 +50,9 @@ pub fn zellij_setup_check_medium(palette: Palette) -> LinePart { ]) } -pub fn zellij_setup_check_short(palette: Palette) -> LinePart { +pub fn zellij_setup_check_short(help: &ModeInfo) -> LinePart { // Run "zellij setup --check" to find issues - let orange_color = palette_match!(palette.orange); + let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Run "), diff --git a/default-plugins/status-bar/src/tip/mod.rs b/default-plugins/status-bar/src/tip/mod.rs index 1249195f9e..9d4fbb70a4 100644 --- a/default-plugins/status-bar/src/tip/mod.rs +++ b/default-plugins/status-bar/src/tip/mod.rs @@ -6,9 +6,8 @@ pub mod utils; use crate::LinePart; use zellij_tile::prelude::*; -pub type TipFn = fn(Palette) -> LinePart; +pub type TipFn = fn(&ModeInfo) -> LinePart; -#[derive(Debug)] pub struct TipBody { pub short: TipFn, pub medium: TipFn, From 7e0a37f8b008f1af79741538c5577a2dc0faf6bd Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 15 Jul 2022 13:49:50 +0200 Subject: [PATCH 70/90] status_bar: abort early when keyvector is empty in `style_key_with_modifier`. --- default-plugins/status-bar/src/main.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index aa21db1dd3..86e8f4decc 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -328,7 +328,16 @@ pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> ret } -pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec> { +pub fn style_key_with_modifier( + keyvec: &[Key], + palette: &Palette, +) -> Vec> { + + // Nothing to do, quit... + if keyvec.is_empty() { + return vec![]; + } + let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, ThemeHue::Light => palette.black, From 07c586b92b26a58131299f4ee094e5fa9f6e4c7e Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 15 Jul 2022 13:50:46 +0200 Subject: [PATCH 71/90] status_bar/tip: Fix all keybindings and make them dynamic given the keybindings really active in the current session. Also display **UNBOUND** is some keybinding is missing from the users config. --- .../status-bar/src/tip/data/compact_layout.rs | 7 +- .../src/tip/data/edit_scrollbuffer.rs | 7 +- .../src/tip/data/floating_panes_mouse.rs | 69 ++++++++++--------- .../tip/data/move_focus_hjkl_tab_switch.rs | 5 ++ .../status-bar/src/tip/data/quicknav.rs | 68 ++++++++++++++---- .../tip/data/send_mouse_click_to_terminal.rs | 24 +++---- .../status-bar/src/tip/data/sync_tab.rs | 5 ++ 7 files changed, 124 insertions(+), 61 deletions(-) diff --git a/default-plugins/status-bar/src/tip/data/compact_layout.rs b/default-plugins/status-bar/src/tip/data/compact_layout.rs index 43249f3725..c3a7b929d8 100644 --- a/default-plugins/status-bar/src/tip/data/compact_layout.rs +++ b/default-plugins/status-bar/src/tip/data/compact_layout.rs @@ -82,12 +82,17 @@ fn add_keybinds(help: &ModeInfo) -> Vec { ); let pane_frames = action_key( &help.get_keybinds_for_mode(InputMode::Pane), - &[Action::TogglePaneFrames], + &[Action::TogglePaneFrames, Action::SwitchToMode(InputMode::Normal)], ); let mut bits = vec![]; bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.push(Style::new().paint(", ")); bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); + + if bits.len() < 2 { + // No keybindings available + bits = vec![Style::new().bold().paint("UNBOUND")]; + } bits } diff --git a/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs b/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs index d501405932..f31af9cce0 100644 --- a/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs +++ b/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs @@ -71,7 +71,7 @@ fn add_keybinds(help: &ModeInfo) -> Vec { &[Action::SwitchToMode(InputMode::Scroll)], ); let pane_frames = action_key( - &help.get_keybinds_for_mode(InputMode::Pane), + &help.get_keybinds_for_mode(InputMode::Scroll), &[ Action::EditScrollback, Action::SwitchToMode(InputMode::Normal), @@ -82,5 +82,10 @@ fn add_keybinds(help: &ModeInfo) -> Vec { bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.push(Style::new().paint(", ")); bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); + + if bits.len() < 2 { + // No keybindings available + bits = vec![Style::new().bold().paint("UNBOUND")]; + } bits } diff --git a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs index 819c992b9e..fbfff1e096 100644 --- a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs +++ b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs @@ -1,12 +1,10 @@ use ansi_term::{ unstyled_len, ANSIString, ANSIStrings, - Color::{Fixed, RGB}, Style, }; -use crate::LinePart; -use zellij_tile::prelude::*; -use zellij_tile_utils::palette_match; +use crate::{LinePart, action_key, style_key_with_modifier}; +use zellij_tile::prelude::{*, actions::Action}; macro_rules! strings { ($ANSIStrings:expr) => {{ @@ -23,47 +21,50 @@ macro_rules! strings { pub fn floating_panes_mouse_full(help: &ModeInfo) -> LinePart { // Tip: Toggle floating panes with Ctrl +

+ and move them with keyboard or mouse - let green_color = palette_match!(help.style.colors.green); - let orange_color = palette_match!(help.style.colors.orange); - - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("Toggle floating panes with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("

"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" and move them with keyboard or mouse"), - ]) + ]; + bits.extend(add_keybinds(help)); + bits.push(Style::new().paint(" and move them with keyboard or mouse")); + strings!(&bits) } pub fn floating_panes_mouse_medium(help: &ModeInfo) -> LinePart { // Tip: Toggle floating panes with Ctrl +

+ - let green_color = palette_match!(help.style.colors.green); - let orange_color = palette_match!(help.style.colors.orange); - strings!(&[ + let mut bits = vec![ Style::new().paint(" Tip: "), Style::new().paint("Toggle floating panes with "), - Style::new().fg(orange_color).bold().paint("Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("

"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - ]) + ]; + bits.extend(add_keybinds(help)); + strings!(&bits) } pub fn floating_panes_mouse_short(help: &ModeInfo) -> LinePart { // Ctrl +

+ => floating panes - let green_color = palette_match!(help.style.colors.green); - let orange_color = palette_match!(help.style.colors.orange); + let mut bits = add_keybinds(help); + bits.push(Style::new().paint(" => floating panes")); + strings!(&bits) +} + +fn add_keybinds(help: &ModeInfo) -> Vec { + let to_pane = action_key( + &help.get_mode_keybinds(), + &[Action::SwitchToMode(InputMode::Pane)], + ); + let pane_frames = action_key( + &help.get_keybinds_for_mode(InputMode::Pane), + &[Action::ToggleFloatingPanes, Action::SwitchToMode(InputMode::Normal)], + ); + + let mut bits = vec![]; + bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); + bits.push(Style::new().paint(", ")); + bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); - strings!(&[ - Style::new().fg(orange_color).bold().paint(" Ctrl"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint("

"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" => floating panes"), - ]) + if bits.len() < 2 { + // No keybindings available + bits = vec![Style::new().bold().paint("UNBOUND")]; + } + bits } diff --git a/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs b/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs index 20292927e5..08caa9536e 100644 --- a/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs +++ b/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs @@ -65,5 +65,10 @@ fn add_keybinds(help: &ModeInfo) -> Vec { bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.push(Style::new().paint(", ")); bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); + + if bits.len() < 2 { + // No keybindings available + bits = vec![Style::new().bold().paint("UNBOUND")]; + } bits } diff --git a/default-plugins/status-bar/src/tip/data/quicknav.rs b/default-plugins/status-bar/src/tip/data/quicknav.rs index e256e243c4..dba08eb6f2 100644 --- a/default-plugins/status-bar/src/tip/data/quicknav.rs +++ b/default-plugins/status-bar/src/tip/data/quicknav.rs @@ -65,28 +65,70 @@ struct Keygroups<'a> { } fn add_keybinds(help: &ModeInfo) -> Keygroups { - let normal_keymap = help.get_keybinds_for_mode(InputMode::Normal); - let new_pane = action_key(&normal_keymap, &[Action::NewPane(None)]); - let move_focus = action_key_group( + let normal_keymap = help.get_mode_keybinds(); + let new_pane_keys = action_key(&normal_keymap, &[Action::NewPane(None)]); + let new_pane = if new_pane_keys.is_empty() { + vec![Style::new().bold().paint("UNBOUND")] + } else { + style_key_with_modifier(&new_pane_keys, &help.style.colors) + }; + + let resize_keys = action_key_group( &normal_keymap, &[ - &[Action::MoveFocus(Direction::Left)], - &[Action::MoveFocus(Direction::Down)], - &[Action::MoveFocus(Direction::Up)], - &[Action::MoveFocus(Direction::Right)], + &[Action::Resize(ResizeDirection::Increase)], + &[Action::Resize(ResizeDirection::Decrease)], ], ); - let resize = action_key_group( + let resize = if resize_keys.is_empty() { + vec![Style::new().bold().paint("UNBOUND")] + } else { + style_key_with_modifier(&resize_keys, &help.style.colors) + }; + + let move_focus_keys = action_key_group( &normal_keymap, &[ - &[Action::Resize(ResizeDirection::Increase)], - &[Action::Resize(ResizeDirection::Decrease)], + &[Action::MoveFocus(Direction::Left)], + &[Action::MoveFocusOrTab(Direction::Left)], + &[Action::MoveFocus(Direction::Down)], + &[Action::MoveFocus(Direction::Up)], + &[Action::MoveFocus(Direction::Right)], + &[Action::MoveFocusOrTab(Direction::Right)], ], ); + // Let's see if we have some pretty groups in common here + let mut arrows = vec![]; + let mut letters = vec![]; + for key in move_focus_keys.into_iter() { + let key_str = key.to_string(); + if key_str.contains("←") + || key_str.contains("↓") + || key_str.contains("↑") + || key_str.contains("→") + { + arrows.push(key); + } else { + letters.push(key); + } + } + let arrows = style_key_with_modifier(&arrows, &help.style.colors); + let letters = style_key_with_modifier(&letters, &help.style.colors); + let move_focus = if arrows.is_empty() && letters.is_empty() { + vec![Style::new().bold().paint("UNBOUND")] + } else if arrows.is_empty() || letters.is_empty() { + arrows.into_iter().chain(letters.into_iter()).collect() + } else { + arrows + .into_iter() + .chain(vec![Style::new().paint(" or ")].into_iter()) + .chain(letters.into_iter()) + .collect() + }; Keygroups { - new_pane: style_key_with_modifier(&new_pane, &help.style.colors), - move_focus: style_key_with_modifier(&move_focus, &help.style.colors), - resize: style_key_with_modifier(&resize, &help.style.colors), + new_pane, + move_focus, + resize, } } diff --git a/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs b/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs index 4d94f7d0c0..7daabeb6dd 100644 --- a/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs +++ b/default-plugins/status-bar/src/tip/data/send_mouse_click_to_terminal.rs @@ -28,10 +28,10 @@ pub fn mouse_click_to_terminal_full(help: &ModeInfo) -> LinePart { strings!(&[ Style::new().paint(" Tip: "), - Style::new().fg(orange_color).bold().paint("SHIFT"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" bypasses Zellij and sends the mouse click directly to the terminal."), + Style::new().fg(orange_color).bold().paint("Shift"), + Style::new().paint(" + <"), + Style::new().fg(green_color).bold().paint("mouse-click"), + Style::new().paint("> bypasses Zellij and sends the mouse click directly to the terminal."), ]) } @@ -41,10 +41,10 @@ pub fn mouse_click_to_terminal_medium(help: &ModeInfo) -> LinePart { let orange_color = palette_match!(help.style.colors.orange); strings!(&[ Style::new().paint(" Tip: "), - Style::new().fg(orange_color).bold().paint("SHIFT"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" sends the click directly to the terminal."), + Style::new().fg(orange_color).bold().paint("Shift"), + Style::new().paint(" + <"), + Style::new().fg(green_color).bold().paint("mouse-click"), + Style::new().paint("> sends the click directly to the terminal."), ]) } @@ -55,9 +55,9 @@ pub fn mouse_click_to_terminal_short(help: &ModeInfo) -> LinePart { strings!(&[ Style::new().paint(" Tip: "), - Style::new().fg(orange_color).bold().paint("SHIFT"), - Style::new().paint(" + "), - Style::new().fg(green_color).bold().paint(""), - Style::new().paint(" => sends click to terminal."), + Style::new().fg(orange_color).bold().paint("Shift"), + Style::new().paint(" + <"), + Style::new().fg(green_color).bold().paint("mouse-click"), + Style::new().paint("> => sends click to terminal."), ]) } diff --git a/default-plugins/status-bar/src/tip/data/sync_tab.rs b/default-plugins/status-bar/src/tip/data/sync_tab.rs index 5ab11a0832..2b31fdf44d 100644 --- a/default-plugins/status-bar/src/tip/data/sync_tab.rs +++ b/default-plugins/status-bar/src/tip/data/sync_tab.rs @@ -60,5 +60,10 @@ fn add_keybinds(help: &ModeInfo) -> Vec { bits.extend(style_key_with_modifier(&to_tab, &help.style.colors)); bits.push(Style::new().paint(", ")); bits.extend(style_key_with_modifier(&sync_tabs, &help.style.colors)); + + if bits.len() < 2 { + // No keybindings available + bits = vec![Style::new().bold().paint("UNBOUND")]; + } bits } From 569b3347590b1282160f0b93a5f914959ec164cb Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 15 Jul 2022 13:57:31 +0200 Subject: [PATCH 72/90] status_bar: Go clippy! --- default-plugins/status-bar/src/main.rs | 6 +----- .../status-bar/src/tip/data/compact_layout.rs | 5 ++++- .../src/tip/data/floating_panes_mouse.rs | 14 +++++++------- .../status-bar/src/tip/data/quicknav.rs | 8 ++++---- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 86e8f4decc..da483ec4d5 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -328,11 +328,7 @@ pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> ret } -pub fn style_key_with_modifier( - keyvec: &[Key], - palette: &Palette, -) -> Vec> { - +pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec> { // Nothing to do, quit... if keyvec.is_empty() { return vec![]; diff --git a/default-plugins/status-bar/src/tip/data/compact_layout.rs b/default-plugins/status-bar/src/tip/data/compact_layout.rs index c3a7b929d8..4413d81faf 100644 --- a/default-plugins/status-bar/src/tip/data/compact_layout.rs +++ b/default-plugins/status-bar/src/tip/data/compact_layout.rs @@ -82,7 +82,10 @@ fn add_keybinds(help: &ModeInfo) -> Vec { ); let pane_frames = action_key( &help.get_keybinds_for_mode(InputMode::Pane), - &[Action::TogglePaneFrames, Action::SwitchToMode(InputMode::Normal)], + &[ + Action::TogglePaneFrames, + Action::SwitchToMode(InputMode::Normal), + ], ); let mut bits = vec![]; diff --git a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs index fbfff1e096..bcf40548ef 100644 --- a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs +++ b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs @@ -1,10 +1,7 @@ -use ansi_term::{ - unstyled_len, ANSIString, ANSIStrings, - Style, -}; +use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style}; -use crate::{LinePart, action_key, style_key_with_modifier}; -use zellij_tile::prelude::{*, actions::Action}; +use crate::{action_key, style_key_with_modifier, LinePart}; +use zellij_tile::prelude::{actions::Action, *}; macro_rules! strings { ($ANSIStrings:expr) => {{ @@ -54,7 +51,10 @@ fn add_keybinds(help: &ModeInfo) -> Vec { ); let pane_frames = action_key( &help.get_keybinds_for_mode(InputMode::Pane), - &[Action::ToggleFloatingPanes, Action::SwitchToMode(InputMode::Normal)], + &[ + Action::ToggleFloatingPanes, + Action::SwitchToMode(InputMode::Normal), + ], ); let mut bits = vec![]; diff --git a/default-plugins/status-bar/src/tip/data/quicknav.rs b/default-plugins/status-bar/src/tip/data/quicknav.rs index dba08eb6f2..a48bb832fd 100644 --- a/default-plugins/status-bar/src/tip/data/quicknav.rs +++ b/default-plugins/status-bar/src/tip/data/quicknav.rs @@ -102,10 +102,10 @@ fn add_keybinds(help: &ModeInfo) -> Keygroups { let mut letters = vec![]; for key in move_focus_keys.into_iter() { let key_str = key.to_string(); - if key_str.contains("←") - || key_str.contains("↓") - || key_str.contains("↑") - || key_str.contains("→") + if key_str.contains('←') + || key_str.contains('↓') + || key_str.contains('↑') + || key_str.contains('→') { arrows.push(key); } else { From 34658d9bead2099044ca084c7656552674fc35f6 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 18 Jul 2022 09:47:04 +0200 Subject: [PATCH 73/90] status_bar: Add documentation and add a new exception group to `action_key_group` that ensures that `hl` and `jk` won't be separated with `|`. --- default-plugins/status-bar/src/main.rs | 31 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index da483ec4d5..f02b78686e 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -301,10 +301,10 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { /// Get key from action pattern(s). /// -/// This macro takes as arguments a `keymap` that is a `Vec<(Key, Vec)>` and contains all -/// keybindings for the current mode and one or more `p` patterns which match a sequence of actions -/// to search for. If within the keymap a sequence of actions matching `p` is found, all keys that -/// trigger the action pattern are returned as vector of `Vec`. +/// This function takes as arguments a `keymap` that is a `Vec<(Key, Vec)>` and contains +/// all keybindings for the current mode and one or more `p` patterns which match a sequence of +/// actions to search for. If within the keymap a sequence of actions matching `p` is found, all +/// keys that trigger the action pattern are returned as vector of `Vec`. // TODO: Accept multiple sequences of patterns, possible separated by '|', and bin them together // into one group under 'text'. pub fn action_key(keymap: &[(Key, Vec)], action: &[Action]) -> Vec { @@ -320,6 +320,7 @@ pub fn action_key(keymap: &[(Key, Vec)], action: &[Action]) -> Vec .collect::>() } +/// Get multiple keys for multiple actions. pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> Vec { let mut ret = vec![]; for action in actions { @@ -328,6 +329,26 @@ pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> ret } +/// Style a vector of [`Key`]s with the given [`Palette`]. +/// +/// Creates a line segment of style ``, with correct theming applied: The brackets have the +/// regular text color, the enclosed keys are painted green and bold. If the keys share a common +/// modifier (See [`get_common_modifier`]), it is printed in front of the keys, painted green and +/// bold, separated with a `+`: `MOD + `. +/// +/// If multiple [`Key`]s are given, the individual keys are separated with a `|` char. This does +/// not apply to the following groups of keys which are treated specially and don't have a +/// separator between them: +/// +/// - "hjkl" +/// - "hl" +/// - "jk" +/// - "←↓↑→" +/// - "←→" +/// - "↓↑" +/// +/// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`] +/// type. pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec> { // Nothing to do, quit... if keyvec.is_empty() { @@ -379,6 +400,8 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec "", + "hl" => "", + "jk" => "", "←↓↑→" => "", "←→" => "", "↓↑" => "", From 8606c4e4ef69c557a6c9ecf44bba78e7c5d4c897 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 18 Jul 2022 09:47:56 +0200 Subject: [PATCH 74/90] status_bar/tip: Detect when key aren't bound correctly and show "UNBOUND" as keyhint instead, then. Previously we would only check the length of the whole keybinding segment, but that isn't a good indicator since most of the bindings require changing modes first, which already adds a variable number of letters to the segment. However, there is not point in showing how to get to a certain mode, if the binding needed in that mode doesn't exist. --- .../status-bar/src/tip/data/compact_layout.rs | 9 ++-- .../src/tip/data/edit_scrollbuffer.rs | 13 +++-- .../src/tip/data/floating_panes_mouse.rs | 16 +++--- .../tip/data/move_focus_hjkl_tab_switch.rs | 51 ++++++++++++------- .../status-bar/src/tip/data/sync_tab.rs | 9 ++-- 5 files changed, 56 insertions(+), 42 deletions(-) diff --git a/default-plugins/status-bar/src/tip/data/compact_layout.rs b/default-plugins/status-bar/src/tip/data/compact_layout.rs index 4413d81faf..dd67ab8f8c 100644 --- a/default-plugins/status-bar/src/tip/data/compact_layout.rs +++ b/default-plugins/status-bar/src/tip/data/compact_layout.rs @@ -88,14 +88,13 @@ fn add_keybinds(help: &ModeInfo) -> Vec { ], ); + if pane_frames.is_empty() { + return vec![Style::new().bold().paint("UNBOUND")]; + } + let mut bits = vec![]; bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.push(Style::new().paint(", ")); bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); - - if bits.len() < 2 { - // No keybindings available - bits = vec![Style::new().bold().paint("UNBOUND")]; - } bits } diff --git a/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs b/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs index f31af9cce0..bef8acba2a 100644 --- a/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs +++ b/default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs @@ -70,7 +70,7 @@ fn add_keybinds(help: &ModeInfo) -> Vec { &help.get_mode_keybinds(), &[Action::SwitchToMode(InputMode::Scroll)], ); - let pane_frames = action_key( + let edit_buffer = action_key( &help.get_keybinds_for_mode(InputMode::Scroll), &[ Action::EditScrollback, @@ -78,14 +78,13 @@ fn add_keybinds(help: &ModeInfo) -> Vec { ], ); + if edit_buffer.is_empty() { + return vec![Style::new().bold().paint("UNBOUND")]; + } + let mut bits = vec![]; bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.push(Style::new().paint(", ")); - bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); - - if bits.len() < 2 { - // No keybindings available - bits = vec![Style::new().bold().paint("UNBOUND")]; - } + bits.extend(style_key_with_modifier(&edit_buffer, &help.style.colors)); bits } diff --git a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs index bcf40548ef..d91c3ed8b4 100644 --- a/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs +++ b/default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs @@ -49,7 +49,7 @@ fn add_keybinds(help: &ModeInfo) -> Vec { &help.get_mode_keybinds(), &[Action::SwitchToMode(InputMode::Pane)], ); - let pane_frames = action_key( + let floating_toggle = action_key( &help.get_keybinds_for_mode(InputMode::Pane), &[ Action::ToggleFloatingPanes, @@ -57,14 +57,16 @@ fn add_keybinds(help: &ModeInfo) -> Vec { ], ); + if floating_toggle.is_empty() { + return vec![Style::new().bold().paint("UNBOUND")]; + } + let mut bits = vec![]; bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); bits.push(Style::new().paint(", ")); - bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); - - if bits.len() < 2 { - // No keybindings available - bits = vec![Style::new().bold().paint("UNBOUND")]; - } + bits.extend(style_key_with_modifier( + &floating_toggle, + &help.style.colors, + )); bits } diff --git a/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs b/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs index 08caa9536e..b7a62ab8b3 100644 --- a/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs +++ b/default-plugins/status-bar/src/tip/data/move_focus_hjkl_tab_switch.rs @@ -1,6 +1,6 @@ use ansi_term::{unstyled_len, ANSIString, ANSIStrings, Style}; -use crate::{action_key, action_key_group, style_key_with_modifier, LinePart}; +use crate::{action_key_group, style_key_with_modifier, LinePart}; use zellij_tile::prelude::{ actions::{Action, Direction}, *, @@ -49,26 +49,41 @@ pub fn move_focus_hjkl_tab_switch_short(help: &ModeInfo) -> LinePart { } fn add_keybinds(help: &ModeInfo) -> Vec { - let to_pane = action_key( - &help.get_mode_keybinds(), - &[Action::SwitchToMode(InputMode::Pane)], - ); - let pane_frames = action_key_group( - &help.get_keybinds_for_mode(InputMode::Normal), + let pane_keymap = help.get_keybinds_for_mode(InputMode::Pane); + let move_focus_keys = action_key_group( + &pane_keymap, &[ - &[Action::MoveFocus(Direction::Left)], - &[Action::MoveFocus(Direction::Down)], + &[Action::MoveFocusOrTab(Direction::Left)], + &[Action::MoveFocusOrTab(Direction::Right)], ], ); - let mut bits = vec![]; - bits.extend(style_key_with_modifier(&to_pane, &help.style.colors)); - bits.push(Style::new().paint(", ")); - bits.extend(style_key_with_modifier(&pane_frames, &help.style.colors)); - - if bits.len() < 2 { - // No keybindings available - bits = vec![Style::new().bold().paint("UNBOUND")]; + // Let's see if we have some pretty groups in common here + let mut arrows = vec![]; + let mut letters = vec![]; + for key in move_focus_keys.into_iter() { + let key_str = key.to_string(); + if key_str.contains('←') + || key_str.contains('↓') + || key_str.contains('↑') + || key_str.contains('→') + { + arrows.push(key); + } else { + letters.push(key); + } + } + let arrows = style_key_with_modifier(&arrows, &help.style.colors); + let letters = style_key_with_modifier(&letters, &help.style.colors); + if arrows.is_empty() && letters.is_empty() { + vec![Style::new().bold().paint("UNBOUND")] + } else if arrows.is_empty() || letters.is_empty() { + arrows.into_iter().chain(letters.into_iter()).collect() + } else { + arrows + .into_iter() + .chain(vec![Style::new().paint(" or ")].into_iter()) + .chain(letters.into_iter()) + .collect() } - bits } diff --git a/default-plugins/status-bar/src/tip/data/sync_tab.rs b/default-plugins/status-bar/src/tip/data/sync_tab.rs index 2b31fdf44d..55331eb3e7 100644 --- a/default-plugins/status-bar/src/tip/data/sync_tab.rs +++ b/default-plugins/status-bar/src/tip/data/sync_tab.rs @@ -56,14 +56,13 @@ fn add_keybinds(help: &ModeInfo) -> Vec { ], ); + if sync_tabs.is_empty() { + return vec![Style::new().bold().paint("UNBOUND")]; + } + let mut bits = vec![]; bits.extend(style_key_with_modifier(&to_tab, &help.style.colors)); bits.push(Style::new().paint(", ")); bits.extend(style_key_with_modifier(&sync_tabs, &help.style.colors)); - - if bits.len() < 2 { - // No keybindings available - bits = vec![Style::new().bold().paint("UNBOUND")]; - } bits } From 5bd2ad9e34d3b8af708f1dc59e6070ca3c6f5d71 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 18 Jul 2022 15:58:05 +0200 Subject: [PATCH 75/90] status_bar/first_line: Show bindings when locked if the user has any configured. --- default-plugins/status-bar/src/first_line.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 7ba43177f2..7a1a008451 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -77,12 +77,10 @@ fn long_tile( first_tile: bool, ) -> LinePart { let key_hint = key.full_text(); - let key_binding = if let KeyMode::Disabled = key.mode { - "".to_string() - } else if key.key.is_none() { - return LinePart::default(); - } else { - key.letter_shortcut(!shared_super) + let key_binding = match (&key.mode, &key.key) { + (KeyMode::Disabled, None) => "".to_string(), + (_, None) => return LinePart::default(), + (_, Some(_)) => key.letter_shortcut(!shared_super), }; let colors = match key.mode { @@ -129,11 +127,12 @@ fn short_tile( shared_super: bool, first_tile: bool, ) -> LinePart { - if key.key.is_none() { - return LinePart::default(); - } + let key_binding = match (&key.mode, &key.key) { + (KeyMode::Disabled, None) => "".to_string(), + (_, None) => return LinePart::default(), + (_, Some(_)) => key.letter_shortcut(!shared_super), + }; - let key_binding = key.letter_shortcut(!shared_super); let colors = match key.mode { KeyMode::Unselected => palette.unselected, KeyMode::UnselectedAlternate => palette.unselected_alternate, From a34f7eff4e2ea0806f54f0be0b53ee6f12d864e7 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 18 Jul 2022 15:58:49 +0200 Subject: [PATCH 76/90] status_bar: Don't consider 'hl', 'jk' groups that don't need a separator in between the letters. --- default-plugins/status-bar/src/main.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index f02b78686e..876ec131ce 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -341,8 +341,6 @@ pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> /// separator between them: /// /// - "hjkl" -/// - "hl" -/// - "jk" /// - "←↓↑→" /// - "←→" /// - "↓↑" @@ -400,8 +398,6 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec "", - "hl" => "", - "jk" => "", "←↓↑→" => "", "←→" => "", "↓↑" => "", From 52e5cd4b99cdfed0c075821e56b5a60b945a89a3 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 19 Jul 2022 09:43:53 +0200 Subject: [PATCH 77/90] status_bar/second_line: Add "search" keybindings for the new Search functionality. --- default-plugins/status-bar/src/second_line.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 23ce69c177..fc01d30294 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -111,6 +111,8 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { use InputMode as IM; use actions::Direction as Dir; use actions::ResizeDirection as RDir; + use actions::SearchDirection as SDir; + use actions::SearchOption as SOpt; let mut old_keymap = mi.get_mode_keybinds(); let s = |string: &str| string.to_string(); @@ -180,7 +182,8 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { (s("Move focus"), s("Move"), focus_keys), (s("New"), s("New"), action_key(&km, &[A::NewTab(None), TO_NORMAL])), (s("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])), - (s("Rename"), s("Rename"), action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), + (s("Rename"), s("Rename"), + action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), (s("Sync"), s("Sync"), action_key(&km, &[A::ToggleActiveSyncTab, TO_NORMAL])), (s("Toggle"), s("Toggle"), action_key(&km, &[A::ToggleTab])), (s("Select pane"), s("Select"), to_normal_key), @@ -205,7 +208,30 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])), (s("Edit scrollback in default editor"), s("Edit"), action_key(&km, &[Action::EditScrollback, TO_NORMAL])), + (s("Enter search term"), s("Search"), + action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])), (s("Select pane"), s("Select"), to_normal_key), + ]} else if mi.mode == IM::EnterSearch { vec![ + (s("When done"), s("Done"), action_key(&km, &[A::SwitchToMode(IM::Search)])), + (s("Cancel"), s("Cancel"), + action_key(&km, &[A::SearchInput(vec![27]), A::SwitchToMode(IM::Scroll)])), + ]} else if mi.mode == IM::Search { vec![ + (s("Scroll"), s("Scroll"), + action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])), + (s("Scroll page"), s("Scroll"), + action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])), + (s("Scroll half page"), s("Scroll"), + action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])), + (s("Enter term"), s("Search"), + action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])), + (s("Search down"), s("Down"), action_key(&km, &[A::Search(SDir::Down)])), + (s("Search up"), s("Up"), action_key(&km, &[A::Search(SDir::Up)])), + (s("Case sensitive"), s("Case"), + action_key(&km, &[A::SearchToggleOption(SOpt::CaseSensitivity)])), + (s("Wrap"), s("Wrap"), + action_key(&km, &[A::SearchToggleOption(SOpt::Wrap)])), + (s("Whole words"), s("Whole"), + action_key(&km, &[A::SearchToggleOption(SOpt::WholeWord)])), ]} else if mi.mode == IM::Session { vec![ (s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])), (s("Select pane"), s("Select"), to_normal_key), From da05202f637748d660c8658a3bb90e0bfcba65f2 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 19 Jul 2022 12:29:40 +0200 Subject: [PATCH 78/90] tests/e2e: Fix snapshots with what the status bar now really displays. --- .../snapshots/zellij__tests__e2e__cases__bracketed_paste.snap | 2 +- .../e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap | 2 +- .../e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap | 2 +- .../zellij__tests__e2e__cases__detach_and_attach_session.snap | 2 +- .../zellij__tests__e2e__cases__focus_pane_with_mouse.snap | 2 +- .../zellij__tests__e2e__cases__focus_tab_with_layout.snap | 2 +- .../e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap | 2 +- .../zellij__tests__e2e__cases__mirrored_sessions-2.snap | 2 +- .../snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap | 2 +- ...cases__multiple_users_in_different_panes_and_same_tab-2.snap | 2 +- ...__cases__multiple_users_in_different_panes_and_same_tab.snap | 2 +- ...__tests__e2e__cases__multiple_users_in_different_tabs-2.snap | 2 +- ...ij__tests__e2e__cases__multiple_users_in_different_tabs.snap | 2 +- ...ests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap | 2 +- ..._tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap | 2 +- .../e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap | 2 +- .../e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap | 2 +- .../zellij__tests__e2e__cases__resize_terminal_window.snap | 2 +- .../zellij__tests__e2e__cases__scrolling_inside_a_pane.snap | 2 +- ...__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap | 2 +- .../zellij__tests__e2e__cases__split_terminals_vertically.snap | 2 +- .../zellij__tests__e2e__cases__start_without_pane_frames.snap | 2 +- .../zellij__tests__e2e__cases__starts_with_one_terminal.snap | 2 +- .../e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap | 2 +- .../zellij__tests__e2e__cases__typing_exit_closes_pane.snap | 2 +- .../snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap | 2 +- .../snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap | 2 +- 27 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap index 842eaed82c..8b66d5ace2 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap index 8a6f9f4001..0d431c3292 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap index 078f7d4eea..603432fdfe 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap index e4f8f1d314..92d2a0378c 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap index 21e1e7b11e..ef923aa2ed 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap index d07e3f2a44..920fde6336 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap index e05e5b14d9..5583430dab 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK  + Ctrl + LOCK  <> PANE  <> TAB  <> RESIZE  <> MOVE  <> SEARCH  <> SESSION  <> QUIT  -- INTERFACE LOCKED -- diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap index 3d4c7368e2..f5c9f44c43 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap @@ -26,4 +26,4 @@ expression: second_runner_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <←↓> Move focus / New / Close / Rename / Sync / Toggle / Select pane + <←→> Move focus / New / Close / Rename / Sync / Toggle / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap index a12aa434ba..68965f48e9 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap @@ -26,4 +26,4 @@ expression: first_runner_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap index 3ad31dcf11..ffcb6b2fd0 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap @@ -26,4 +26,4 @@ expression: second_runner_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap index 4abaae5907..797448a6a1 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap @@ -26,4 +26,4 @@ expression: first_runner_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap index 8c6b4face8..cd4006d5b6 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap @@ -26,4 +26,4 @@ expression: second_runner_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap index 19f23c390f..8856f1f9f3 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap @@ -26,4 +26,4 @@ expression: first_runner_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap index 74fb1a5710..d7328b8db3 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap @@ -26,4 +26,4 @@ expression: second_runner_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap index 2aec1c4d62..6bc0369c93 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap @@ -26,4 +26,4 @@ expression: first_runner_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap index 032a03caa5..2d84b3f39c 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap index d78a61df48..f03656301c 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap index 513b0ec67c..9587ade245 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └────────────────────────────────────────────────┘└────────────────────────────────────────────────┘ Ctrl + g  p  t  n  h  s  o  q  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + QuickNav: Alt + / Alt + <←↓↑→> or Alt + / Alt + <+|=|-> / diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap index 34630721b9..ff4ce54413 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││li█e21 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - <↓↑> Scroll / <→←> Scroll page / Scroll half page / Edit scrollback in default editor / Select pane + <↓↑> Scroll / Scroll / Scroll / Edit / Search / Select diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap index 2bcdfaede9..ec416eeab4 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││li█e19 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap index 76411adb64..22b796279e 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap index 51289dbb22..f066d20782 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap @@ -26,4 +26,4 @@ $ │$ █ │ │ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap index c99691342c..1f4ff4f02d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap index 1c85d159ec..05985a5f27 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap index 81b3e55473..3a1643b16d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap index c5c77a31c2..03b44ebb1e 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap index f80671e137..ed9a8e20e8 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. + Tip: Alt + => new pane. Alt + <←↓↑→> or Alt + => navigate. Alt + <+|=|-> => resize pane. From 52ef7a077d4c436f8899b9bdc723d8b0c5cb003d Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 21 Jul 2022 12:42:06 +0200 Subject: [PATCH 79/90] status_bar: Remove old comments --- default-plugins/status-bar/src/first_line.rs | 3 +-- default-plugins/status-bar/src/main.rs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 7a1a008451..38e6a510fd 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -32,9 +32,8 @@ enum KeyMode { } impl KeyShortcut { - //pub fn new(mode: KeyMode, action: KeyAction, bind: KeyBind) -> Self { pub fn new(mode: KeyMode, action: KeyAction, key: Option) -> Self { - KeyShortcut { mode, action, key } //, bind } + KeyShortcut { mode, action, key } } pub fn full_text(&self) -> String { diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 876ec131ce..fcd2257d45 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -305,8 +305,6 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { /// all keybindings for the current mode and one or more `p` patterns which match a sequence of /// actions to search for. If within the keymap a sequence of actions matching `p` is found, all /// keys that trigger the action pattern are returned as vector of `Vec`. -// TODO: Accept multiple sequences of patterns, possible separated by '|', and bin them together -// into one group under 'text'. pub fn action_key(keymap: &[(Key, Vec)], action: &[Action]) -> Vec { keymap .iter() From 1b373ae10a22cb6ba07b025fcd0ca1edcc1ebcd3 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 21 Jul 2022 12:42:55 +0200 Subject: [PATCH 80/90] status_bar/first_line: Rename 'long_tile' to the more descriptive name 'mode_shortcut', which better describes what this function does. --- default-plugins/status-bar/src/first_line.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 38e6a510fd..5899e4d03a 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -68,7 +68,7 @@ impl KeyShortcut { } } -fn long_tile( +fn mode_shortcut( key: &KeyShortcut, palette: ColoredElements, separator: &str, @@ -168,7 +168,7 @@ fn key_indicators( let shared_super = line_part.len > 0; for ctrl_key in keys { let line_empty = line_part.len == 0; - let key = long_tile(ctrl_key, palette, separator, shared_super, line_empty); + let key = mode_shortcut(ctrl_key, palette, separator, shared_super, line_empty); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } From 2e625137698fa0c93843a9ddbbf7741613912f09 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 21 Jul 2022 12:43:34 +0200 Subject: [PATCH 81/90] status_bar/first_line: Fix spacing in simple UI where the modifier would be shows as `Ctrl +`, without a trailing space. This isn't an issue in regular mode, where we have the spacing from the arrow gaps (`>>`) that "simulates" this effect. --- default-plugins/status-bar/src/first_line.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 5899e4d03a..51c965f4c0 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -247,7 +247,14 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) -> LinePart { // Find a common modifier if any let prefix_text = match get_common_modifier(mode_switch_keys(mode_info).iter().collect()) { - Some(text) => format!(" {} +", text), + Some(text) => { + if mode_info.capabilities.arrow_fonts { + // Add extra space in simplified ui + format!(" {} + ", text) + } else { + format!(" {} +", text) + } + }, _ => return LinePart::default(), }; From 0513979aa4cdab0a717af998bf553fdd9f7cbc47 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 21 Jul 2022 12:44:58 +0200 Subject: [PATCH 82/90] status_bar: Refactor and rename `ctrl_keys` so it doesn't rely on some "external" index for operation any more. --- default-plugins/status-bar/src/first_line.rs | 61 ++++++++++++++------ default-plugins/status-bar/src/main.rs | 5 +- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 51c965f4c0..95c5feaafe 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -12,6 +12,7 @@ struct KeyShortcut { key: Option, } +#[derive(PartialEq)] enum KeyAction { Lock, Pane, @@ -283,7 +284,37 @@ pub fn to_char(kv: Vec) -> Option { key.cloned() } -pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { +/// Get the [`KeyShortcut`] for a specific [`InputMode`]. +/// +/// Iterates over the contents of `shortcuts` to find the [`KeyShortcut`] with the [`KeyAction`] +/// matching the [`InputMode`]. Returns a mutable reference to the entry in `shortcuts` if a match +/// is found or `None` otherwise. +/// +/// In case multiple entries in `shortcuts` match `mode` (which shouldn't happen), the first match +/// is returned. +fn get_key_shortcut_for_mode<'a>( + shortcuts: &'a mut [KeyShortcut], + mode: &InputMode, +) -> Option<&'a mut KeyShortcut> { + let key_action = match mode { + InputMode::Normal | InputMode::Prompt | InputMode::Tmux => return None, + InputMode::Locked => KeyAction::Lock, + InputMode::Pane | InputMode::RenamePane => KeyAction::Pane, + InputMode::Tab | InputMode::RenameTab => KeyAction::Tab, + InputMode::Resize => KeyAction::Resize, + InputMode::Move => KeyAction::Move, + InputMode::Scroll | InputMode::Search | InputMode::EnterSearch => KeyAction::Search, + InputMode::Session => KeyAction::Session, + }; + for shortcut in shortcuts.iter_mut() { + if shortcut.action == key_action { + return Some(shortcut); + } + } + None +} + +pub fn first_line(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { let supports_arrow_fonts = !help.capabilities.arrow_fonts; let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); let binds = &help.get_mode_keybinds(); @@ -343,24 +374,16 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { ), ]; - let mode_index = match &help.mode { - InputMode::Normal | InputMode::Prompt | InputMode::Tmux => None, - InputMode::Locked => { - for key in default_keys.iter_mut().skip(1) { - key.mode = KeyMode::Disabled; - } - Some(0) - }, - InputMode::Pane | InputMode::RenamePane => Some(1), - InputMode::Tab | InputMode::RenameTab => Some(2), - InputMode::Resize => Some(3), - InputMode::Move => Some(4), - InputMode::Scroll | InputMode::Search | InputMode::EnterSearch => Some(5), - InputMode::Session => Some(6), - }; - if let Some(index) = mode_index { - default_keys[index].mode = KeyMode::Selected; - default_keys[index].key = to_char(action_key(binds, &[TO_NORMAL])); + if let Some(key_shortcut) = get_key_shortcut_for_mode(&mut default_keys, &help.mode) { + key_shortcut.mode = KeyMode::Selected; + key_shortcut.key = to_char(action_key(binds, &[TO_NORMAL])); + } + + // In locked mode we must disable all other mode keybindings + if help.mode == InputMode::Locked { + for key in default_keys.iter_mut().skip(1) { + key.mode = KeyMode::Disabled; + } } if help.mode == InputMode::Tmux { diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index fcd2257d45..426daeb0b0 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -13,7 +13,7 @@ use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; use zellij_tile_utils::{palette_match, style}; -use first_line::ctrl_keys; +use first_line::first_line; use second_line::{ floating_panes_are_visible, fullscreen_panes_to_hide, keybinds, locked_floating_panes_are_visible, locked_fullscreen_panes_to_hide, system_clipboard_error, @@ -212,8 +212,7 @@ impl ZellijPlugin for State { "" }; - let first_line = ctrl_keys(&self.mode_info, cols, separator); - + let first_line = first_line(&self.mode_info, cols, separator); let second_line = self.second_line(cols); let background = match self.mode_info.style.colors.theme_hue { From 889177835e117c5230f4899ffdbc27af9a17b399 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Thu, 21 Jul 2022 12:46:08 +0200 Subject: [PATCH 83/90] status_bar: Add unit tests to shared functions and fix a bug in the process where certain `Ctrl` keybindings would be displayed wrong. --- default-plugins/status-bar/src/main.rs | 363 ++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 3 deletions(-) diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 426daeb0b0..e2b2be919f 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -274,8 +274,8 @@ impl State { /// Get a common modifier key from a key vector. /// -/// Iterates over all keys, skipping keys mentioned in `to_ignore` and returns any found common -/// modifier key. +/// Iterates over all keys and returns any found common modifier key. Possible modifiers that will +/// be detected are "Ctrl" and "Alt". pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { let mut modifier = ""; let mut new_modifier; @@ -318,6 +318,8 @@ pub fn action_key(keymap: &[(Key, Vec)], action: &[Action]) -> Vec } /// Get multiple keys for multiple actions. +/// +/// An extension of [`action_key`] that iterates over all action tuples and collects the results. pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> Vec { let mut ret = vec![]; for action in actions { @@ -383,7 +385,7 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec format!("{}", c), + Key::Ctrl(c) => format!("{}", Key::Char(*c)), Key::Alt(c) => format!("{}", c), _ => format!("{}", key), } @@ -413,3 +415,358 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec Vec<(Key, Vec)> { + vec![ + (Key::Char('a'), vec![Action::Quit]), + (Key::Ctrl('b'), vec![Action::ScrollUp]), + (Key::Ctrl('d'), vec![Action::ScrollDown]), + ( + Key::Alt(CharOrArrow::Char('c')), + vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)], + ), + ( + Key::Char('1'), + vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)], + ), + ] + } + + #[test] + fn action_key_long_pattern_match_exact() { + let keymap = big_keymap(); + let ret = action_key(&keymap, &[Action::ScrollDown, TO_NORMAL]); + assert_eq!(ret, vec![Key::Alt(CharOrArrow::Char('c'))]); + } + + #[test] + fn action_key_long_pattern_match_too_short() { + let keymap = big_keymap(); + let ret = action_key(&keymap, &[TO_NORMAL]); + assert_eq!(ret, Vec::new()); + } + + #[test] + fn action_key_group_single_pattern() { + let keymap = big_keymap(); + let ret = action_key_group(&keymap, &[&[Action::Quit]]); + assert_eq!(ret, vec![Key::Char('a')]); + } + + #[test] + fn action_key_group_two_patterns() { + let keymap = big_keymap(); + let ret = action_key_group(&keymap, &[&[Action::ScrollDown], &[Action::ScrollUp]]); + // Mind the order! + assert_eq!(ret, vec![Key::Ctrl('d'), Key::Ctrl('b')]); + } + + fn get_palette() -> Palette { + Palette::default() + } + + #[test] + fn style_key_with_modifier_only_chars() { + let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "".to_string()) + } + + #[test] + fn style_key_with_modifier_special_group_hjkl() { + let keyvec = vec![ + Key::Char('h'), + Key::Char('j'), + Key::Char('k'), + Key::Char('l'), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "".to_string()) + } + + #[test] + fn style_key_with_modifier_special_group_hjkl_broken() { + // Sorted the wrong way + let keyvec = vec![ + Key::Char('h'), + Key::Char('k'), + Key::Char('j'), + Key::Char('l'), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "".to_string()) + } + + #[test] + fn style_key_with_modifier_special_group_all_arrows() { + let keyvec = vec![ + Key::Char('←'), + Key::Char('↓'), + Key::Char('↑'), + Key::Char('→'), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "<←↓↑→>".to_string()) + } + + #[test] + fn style_key_with_modifier_special_group_left_right_arrows() { + let keyvec = vec![Key::Char('←'), Key::Char('→')]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "<←→>".to_string()) + } + + #[test] + fn style_key_with_modifier_special_group_down_up_arrows() { + let keyvec = vec![Key::Char('↓'), Key::Char('↑')]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "<↓↑>".to_string()) + } + + #[test] + fn style_key_with_modifier_common_ctrl_modifier_chars() { + let keyvec = vec![ + Key::Ctrl('a'), + Key::Ctrl('b'), + Key::Ctrl('c'), + Key::Ctrl('d'), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "Ctrl + ".to_string()) + } + + #[test] + fn style_key_with_modifier_common_alt_modifier_chars() { + let keyvec = vec![ + Key::Alt(CharOrArrow::Char('a')), + Key::Alt(CharOrArrow::Char('b')), + Key::Alt(CharOrArrow::Char('c')), + Key::Alt(CharOrArrow::Char('d')), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "Alt + ".to_string()) + } + + #[test] + fn style_key_with_modifier_common_alt_modifier_with_special_group_all_arrows() { + let keyvec = vec![ + Key::Alt(CharOrArrow::Direction(Direction::Left)), + Key::Alt(CharOrArrow::Direction(Direction::Down)), + Key::Alt(CharOrArrow::Direction(Direction::Up)), + Key::Alt(CharOrArrow::Direction(Direction::Right)), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "Alt + <←↓↑→>".to_string()) + } + + #[test] + fn style_key_with_modifier_ctrl_alt_char_mixed() { + let keyvec = vec![ + Key::Alt(CharOrArrow::Char('a')), + Key::Ctrl('b'), + Key::Char('c'), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "".to_string()) + } + + #[test] + fn style_key_with_modifier_unprintables() { + let keyvec = vec![ + Key::Backspace, + Key::Char('\n'), + Key::Char(' '), + Key::Char('\t'), + Key::PageDown, + Key::Delete, + Key::Home, + Key::End, + Key::Insert, + Key::BackTab, + Key::Esc, + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "".to_string()) + } + + #[test] + fn style_key_with_modifier_unprintables_with_common_ctrl_modifier() { + let keyvec = vec![ + Key::Ctrl('\n'), + Key::Ctrl(' '), + Key::Ctrl('\t'), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "Ctrl + ".to_string()) + } + + #[test] + fn style_key_with_modifier_unprintables_with_common_alt_modifier() { + let keyvec = vec![ + Key::Alt(CharOrArrow::Char('\n')), + Key::Alt(CharOrArrow::Char(' ')), + Key::Alt(CharOrArrow::Char('\t')), + ]; + let palette = get_palette(); + + let ret = style_key_with_modifier(&keyvec, &palette); + let ret = unstyle(&ANSIStrings(&ret)); + + assert_eq!(ret, "Alt + ".to_string()) + } +} From 3a06d5f05a0fad18d0bc2312d620409d717a42d1 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 22 Jul 2022 12:56:31 +0200 Subject: [PATCH 84/90] status_bar/first_line: Rename functions responsible for printing the long and short shortcut keyhint tiles. Also add some documentation that explains their purpose and the arguments they accept. --- default-plugins/status-bar/src/first_line.rs | 45 ++++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 95c5feaafe..a5afec2215 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -69,7 +69,26 @@ impl KeyShortcut { } } -fn mode_shortcut( +/// Generate long mode shortcut tile. +/// +/// A long mode shortcut tile consists of a leading and trailing `separator`, a keybinding enclosed +/// in `<>` brackets and the name of the mode displayed in capitalized letters next to it. For +/// example, the default long mode shortcut tile for "Locked" mode is: ` LOCK `. +/// +/// # Arguments +/// +/// - `key`: A [`KeyShortcut`] that defines how the tile is displayed (active/disabled/...), what +/// action it belongs to (roughly equivalent to [`InputMode`]s) and the keybinding to trigger +/// this action. +/// - `palette`: A structure holding styling information. +/// - `separator`: The separator printed before and after the mode shortcut tile. The default is an +/// arrow head-like separator. +/// - `shared_super`: If set to true, all mode shortcut keybindings share a common modifier (see +/// [`get_common_modifier`]) and the modifier belonging to the keybinding is **not** printed in +/// the shortcut tile. +/// - `first_tile`: If set to true, the leading separator for this tile will be ommited so no gap +/// appears on the screen. +fn long_mode_shortcut( key: &KeyShortcut, palette: ColoredElements, separator: &str, @@ -120,7 +139,25 @@ fn mode_shortcut( } } -fn short_tile( +/// Generate short mode shortcut tile. +/// +/// A short mode shortcut tile consists of a leading and trailing `separator` and a keybinding. For +/// example, the default short mode shortcut tile for "Locked" mode is: ` g `. +/// +/// # Arguments +/// +/// - `key`: A [`KeyShortcut`] that defines how the tile is displayed (active/disabled/...), what +/// action it belongs to (roughly equivalent to [`InputMode`]s) and the keybinding to trigger +/// this action. +/// - `palette`: A structure holding styling information. +/// - `separator`: The separator printed before and after the mode shortcut tile. The default is an +/// arrow head-like separator. +/// - `shared_super`: If set to true, all mode shortcut keybindings share a common modifier (see +/// [`get_common_modifier`]) and the modifier belonging to the keybinding is **not** printed in +/// the shortcut tile. +/// - `first_tile`: If set to true, the leading separator for this tile will be ommited so no gap +/// appears on the screen. +fn short_mode_shortcut( key: &KeyShortcut, palette: ColoredElements, separator: &str, @@ -169,7 +206,7 @@ fn key_indicators( let shared_super = line_part.len > 0; for ctrl_key in keys { let line_empty = line_part.len == 0; - let key = mode_shortcut(ctrl_key, palette, separator, shared_super, line_empty); + let key = long_mode_shortcut(ctrl_key, palette, separator, shared_super, line_empty); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -182,7 +219,7 @@ fn key_indicators( let shared_super = line_part.len > 0; for ctrl_key in keys { let line_empty = line_part.len == 0; - let key = short_tile(ctrl_key, palette, separator, shared_super, line_empty); + let key = short_mode_shortcut(ctrl_key, palette, separator, shared_super, line_empty); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } From a9327f609cbfb265966e8949973e25681950ca28 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 22 Jul 2022 12:57:34 +0200 Subject: [PATCH 85/90] status_bar/tips: Remove stray "/" in quicknav tip --- default-plugins/status-bar/src/tip/data/quicknav.rs | 1 - .../zellij__tests__e2e__cases__resize_terminal_window.snap | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/default-plugins/status-bar/src/tip/data/quicknav.rs b/default-plugins/status-bar/src/tip/data/quicknav.rs index a48bb832fd..59b604a701 100644 --- a/default-plugins/status-bar/src/tip/data/quicknav.rs +++ b/default-plugins/status-bar/src/tip/data/quicknav.rs @@ -54,7 +54,6 @@ pub fn quicknav_short(help: &ModeInfo) -> LinePart { bits.extend(groups.move_focus); bits.push(Style::new().paint(" / ")); bits.extend(groups.resize); - bits.push(Style::new().paint(" / ")); strings!(&bits) } diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap index 9587ade245..de1ef639a1 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └────────────────────────────────────────────────┘└────────────────────────────────────────────────┘ Ctrl + g  p  t  n  h  s  o  q  - QuickNav: Alt + / Alt + <←↓↑→> or Alt + / Alt + <+|=|-> / + QuickNav: Alt + / Alt + <←↓↑→> or Alt + / Alt + <+|=|-> From 1d8ef803e50a52b6590beb26b161bf5bfee0bb7e Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 22 Jul 2022 12:57:59 +0200 Subject: [PATCH 86/90] utils/layout: Remove old comments introduced when rewriting `SplitSize::Percent` to not hold an `f64` type. --- zellij-utils/src/input/layout.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index aba13e64f3..3f0b392957 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -60,28 +60,6 @@ pub enum SplitSize { Fixed(usize), // An absolute number of columns or rows } -// FIXME: This is an ugly hack around the fact that f64 as type (which was previously what -// `SplitSize::Percent(T)` used as inner type `T`) doesn't implement `Eq` and `Hash` traits. So we -// make a custom type that encloses a u64 instead and add conversion methods to/from f64. Of course -// that breaks the interface and all of the tests. This type is, through a long chain of structs, -// part of the `zellij_utils::data::ModeInfo` struct that derives the `Eq` trait on itself, hence -// this is needed here. -//#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] -//pub struct Percent(pub u64); -// -//impl From for Percent { -// fn from(val: f64) -> Percent { -// Percent((val * 10000.0) as u64) -// } -//} -// -//impl From for f64 { -// fn from(val: Percent) -> f64 { -// let Percent(val) = val; -// (val as f64) / 10000.0 -// } -//} - #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub enum Run { #[serde(rename = "plugin")] From 96b28d7558446b01f7c7e972e8faf12a6ae252a1 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 22 Jul 2022 12:59:07 +0200 Subject: [PATCH 87/90] status_bar: Add "regex" as test dependency We use regular expressions to strip all ANSI escape sequences in the strings that are produced by the plugin functions during testing. We do not test for the style information, but merely for the raw text. --- Cargo.lock | 1 + default-plugins/status-bar/Cargo.toml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0b85eeaa95..eca15cb00a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2329,6 +2329,7 @@ dependencies = [ "colored", "lazy_static", "rand 0.8.5", + "regex", "serde", "serde_json", "thiserror", diff --git a/default-plugins/status-bar/Cargo.toml b/default-plugins/status-bar/Cargo.toml index 4c611dc881..a2ca3175a4 100644 --- a/default-plugins/status-bar/Cargo.toml +++ b/default-plugins/status-bar/Cargo.toml @@ -15,3 +15,6 @@ serde_json = "1.0" thiserror = "1.0.30" zellij-tile = { path = "../../zellij-tile" } zellij-tile-utils = { path = "../../zellij-tile-utils" } + +[dev-dependencies] +regex = "1" From 80a84a3c6e4f66c2fb7cadc0a928891f5115650c Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 22 Jul 2022 13:00:09 +0200 Subject: [PATCH 88/90] status_bar: Implement unit tests --- default-plugins/status-bar/src/first_line.rs | 405 ++++++++++++++++++ default-plugins/status-bar/src/main.rs | 48 +-- default-plugins/status-bar/src/second_line.rs | 241 +++++++++++ 3 files changed, 667 insertions(+), 27 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index a5afec2215..ae8fc70e73 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -434,3 +434,408 @@ pub fn first_line(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart key_indicators(max_len, &default_keys, colored_elements, separator, help) } + +#[cfg(test)] +/// Unit tests. +/// +/// Note that we cheat a little here, because the number of things one may want to test is endless, +/// and creating a Mockup of [`ModeInfo`] by hand for all these testcases is nothing less than +/// torture. Hence, we test the most atomic units thoroughly ([`long_mode_shortcut`] and +/// [`short_mode_shortcut`]) and then test the public API ([`first_line`]) to ensure correct +/// operation. +mod tests { + use super::*; + + fn colored_elements() -> ColoredElements { + let palette = Palette::default(); + color_elements(palette, false) + } + + // Strip style information from `LinePart` and return a raw String instead + fn unstyle(line_part: LinePart) -> String { + let string = line_part.to_string(); + + let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap(); + let string = re.replace_all(&string, "".to_string()); + + string.to_string() + } + + #[test] + fn long_mode_shortcut_selected_with_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Displayed like selected(alternate), but different styling + fn long_mode_shortcut_unselected_with_binding() { + let key = KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Treat exactly like "unselected" variant + fn long_mode_shortcut_unselected_alternate_with_binding() { + let key = KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // KeyShortcuts without binding are only displayed when "disabled" (for locked mode indications) + fn long_mode_shortcut_selected_without_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + // First tile doesn't print a starting separator + fn long_mode_shortcut_selected_with_binding_first_tile() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, true); + let ret = unstyle(ret); + + assert_eq!(ret, " <0> SESSION +".to_string()); + } + + #[test] + // Modifier is the superkey, mustn't appear in angled brackets + fn long_mode_shortcut_selected_with_ctrl_binding_shared_superkey() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", true, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Modifier must be in the angled brackets + fn long_mode_shortcut_selected_with_ctrl_binding_no_shared_superkey() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ SESSION +".to_string()); + } + + #[test] + // Must be displayed as usual, but it is styled to be greyed out which we don't test here + fn long_mode_shortcut_disabled_with_binding() { + let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Must be displayed but without keybinding + fn long_mode_shortcut_disabled_without_binding() { + let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, None); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <> SESSION +".to_string()); + } + + #[test] + // Test all at once + // Note that when "shared_super" is true, the tile **cannot** be the first on the line, so we + // ignore **first** here. + fn long_mode_shortcut_selected_with_ctrl_binding_and_shared_super_and_first_tile() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", true, true); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_ctrl_binding_no_shared_super() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ Ctrl+0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_ctrl_binding_shared_super() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", true, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_binding_first_tile() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, true); + let ret = unstyle(ret); + + assert_eq!(ret, " 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_with_binding() { + let key = KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_alternate_with_binding() { + let key = KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_disabled_with_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_without_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_without_binding() { + let key = KeyShortcut::new(KeyMode::Unselected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_alternate_without_binding() { + let key = KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + fn short_mode_shortcut_disabled_without_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + // Observe: Modes missing in between aren't displayed! + fn first_line_default_layout_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 500, ">"); + let ret = unstyle(ret); + + assert_eq!( + ret, + " Ctrl + >> PANE >> RESIZE >> MOVE >".to_string() + ); + } + + #[test] + fn first_line_default_layout_no_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Char('c'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 500, ">"); + let ret = unstyle(ret); + + assert_eq!( + ret, + " PANE >> RESIZE >> MOVE >".to_string() + ); + } + + #[test] + fn first_line_default_layout_unprintables() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), + (Key::Backspace, vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]), + (Key::Char('\t'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Left, vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 500, ">"); + let ret = unstyle(ret); + + assert_eq!( + ret, + " LOCK >> PANE >> TAB >> RESIZE >> <←> MOVE >" + .to_string() + ); + } + + #[test] + fn first_line_short_layout_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Tab)]), + (Key::Ctrl('d'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Ctrl('e'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 50, ">"); + let ret = unstyle(ret); + + assert_eq!(ret, " Ctrl + >> a >> b >> c >> d >> e >".to_string()); + } + + #[test] + fn first_line_short_simplified_ui_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 30, ""); + let ret = unstyle(ret); + + assert_eq!(ret, " Ctrl + a b c ".to_string()); + } +} diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index e2b2be919f..38728d254c 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -417,17 +417,28 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec Vec<(Key, Vec)> { + vec![ + (Key::Char('a'), vec![Action::Quit]), + (Key::Ctrl('b'), vec![Action::ScrollUp]), + (Key::Ctrl('d'), vec![Action::ScrollDown]), + ( + Key::Alt(CharOrArrow::Char('c')), + vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)], + ), + ( + Key::Char('1'), + vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)], + ), + ] + } #[test] fn common_modifier_with_ctrl_keys() { @@ -523,22 +534,6 @@ mod tests { assert_eq!(ret, Vec::new()); } - fn big_keymap() -> Vec<(Key, Vec)> { - vec![ - (Key::Char('a'), vec![Action::Quit]), - (Key::Ctrl('b'), vec![Action::ScrollUp]), - (Key::Ctrl('d'), vec![Action::ScrollDown]), - ( - Key::Alt(CharOrArrow::Char('c')), - vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)], - ), - ( - Key::Char('1'), - vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)], - ), - ] - } - #[test] fn action_key_long_pattern_match_exact() { let keymap = big_keymap(); @@ -737,16 +732,15 @@ mod tests { let ret = style_key_with_modifier(&keyvec, &palette); let ret = unstyle(&ANSIStrings(&ret)); - assert_eq!(ret, "".to_string()) + assert_eq!( + ret, + "".to_string() + ) } #[test] fn style_key_with_modifier_unprintables_with_common_ctrl_modifier() { - let keyvec = vec![ - Key::Ctrl('\n'), - Key::Ctrl(' '), - Key::Ctrl('\t'), - ]; + let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')]; let palette = get_palette(); let ret = style_key_with_modifier(&keyvec, &palette); diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index fc01d30294..26afcd94f2 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -521,3 +521,244 @@ pub fn locked_floating_panes_are_visible(palette: &Palette) -> LinePart { len, } } + +#[cfg(test)] +/// Unit tests. +/// +/// Note that we cheat a little here, because the number of things one may want to test is endless, +/// and creating a Mockup of [`ModeInfo`] by hand for all these testcases is nothing less than +/// torture. Hence, we test the most atomic unit thoroughly ([`full_length_shortcut`] and then test +/// the public API ([`keybinds`]) to ensure correct operation. +mod tests { + use super::*; + + // Strip style information from `LinePart` and return a raw String instead + fn unstyle(line_part: LinePart) -> String { + let string = line_part.to_string(); + + let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap(); + let string = re.replace_all(&string, "".to_string()); + + string.to_string() + } + + fn get_palette() -> Palette { + Palette::default() + } + + #[test] + fn full_length_shortcut_with_key() { + let keyvec = vec![Key::Char('a')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_key_first_element() { + let keyvec = vec![Key::Char('a')]; + let palette = get_palette(); + + let ret = full_length_shortcut(true, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " Foobar"); + } + + #[test] + // When there is no binding, we print no shortcut either + fn full_length_shortcut_without_key() { + let keyvec = vec![]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, ""); + } + + #[test] + fn full_length_shortcut_with_key_unprintable_1() { + let keyvec = vec![Key::Char('\n')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_key_unprintable_2() { + let keyvec = vec![Key::Backspace]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_ctrl_key() { + let keyvec = vec![Key::Ctrl('a')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Ctrl + Foobar"); + } + + #[test] + fn full_length_shortcut_with_alt_key() { + let keyvec = vec![Key::Alt(CharOrArrow::Char('a'))]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Alt + Foobar"); + } + + #[test] + fn full_length_shortcut_with_homogenous_key_group() { + let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_heterogenous_key_group() { + let keyvec = vec![Key::Char('a'), Key::Ctrl('b'), Key::Char('\n')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_key_group_shared_ctrl_modifier() { + let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Ctrl + Foobar"); + } + //pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart { + + #[test] + // Note how it leaves out elements that don't exist! + fn keybinds_wide() { + let mode_info = ModeInfo { + mode: InputMode::Pane, + keybinds: vec![( + InputMode::Pane, + vec![ + (Key::Left, vec![Action::MoveFocus(actions::Direction::Left)]), + (Key::Down, vec![Action::MoveFocus(actions::Direction::Down)]), + (Key::Up, vec![Action::MoveFocus(actions::Direction::Up)]), + ( + Key::Right, + vec![Action::MoveFocus(actions::Direction::Right)], + ), + (Key::Char('n'), vec![Action::NewPane(None), TO_NORMAL]), + (Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]), + ( + Key::Char('f'), + vec![Action::ToggleFocusFullscreen, TO_NORMAL], + ), + ], + )], + ..ModeInfo::default() + }; + + let ret = keybinds(&mode_info, "quicknav", 500); + let ret = unstyle(ret); + + assert_eq!( + ret, + " <←↓↑→> Move focus / New / Close / Fullscreen" + ); + } + + #[test] + // Note how "Move focus" becomes "Move" + fn keybinds_tight_width() { + let mode_info = ModeInfo { + mode: InputMode::Pane, + keybinds: vec![( + InputMode::Pane, + vec![ + (Key::Left, vec![Action::MoveFocus(actions::Direction::Left)]), + (Key::Down, vec![Action::MoveFocus(actions::Direction::Down)]), + (Key::Up, vec![Action::MoveFocus(actions::Direction::Up)]), + ( + Key::Right, + vec![Action::MoveFocus(actions::Direction::Right)], + ), + (Key::Char('n'), vec![Action::NewPane(None), TO_NORMAL]), + (Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]), + ( + Key::Char('f'), + vec![Action::ToggleFocusFullscreen, TO_NORMAL], + ), + ], + )], + ..ModeInfo::default() + }; + + let ret = keybinds(&mode_info, "quicknav", 35); + let ret = unstyle(ret); + + assert_eq!(ret, " <←↓↑→> Move / New ... "); + } + + #[test] + fn keybinds_wide_weird_keys() { + let mode_info = ModeInfo { + mode: InputMode::Pane, + keybinds: vec![( + InputMode::Pane, + vec![ + ( + Key::Ctrl('a'), + vec![Action::MoveFocus(actions::Direction::Left)], + ), + ( + Key::Ctrl('\n'), + vec![Action::MoveFocus(actions::Direction::Down)], + ), + ( + Key::Ctrl('1'), + vec![Action::MoveFocus(actions::Direction::Up)], + ), + ( + Key::Ctrl(' '), + vec![Action::MoveFocus(actions::Direction::Right)], + ), + (Key::Backspace, vec![Action::NewPane(None), TO_NORMAL]), + (Key::Esc, vec![Action::CloseFocus, TO_NORMAL]), + (Key::End, vec![Action::ToggleFocusFullscreen, TO_NORMAL]), + ], + )], + ..ModeInfo::default() + }; + + let ret = keybinds(&mode_info, "quicknav", 500); + let ret = unstyle(ret); + + assert_eq!(ret, " Ctrl + Move focus / New / Close / Fullscreen"); + } +} From dc7ed868dbedaa7789c6bcd2f2ba168a9a49c7fe Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 26 Jul 2022 15:46:45 +0200 Subject: [PATCH 89/90] Makefile: Always run tests on host triple This allows the unit tests for all plugins to be run on the host as well (because their default compilation target is wasm32-wasi). --- Makefile.toml | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index 16057604d0..f8ade7e644 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -18,18 +18,9 @@ dependencies = [ "clippy", ] -# Patching the default flows to skip testing of wasm32-wasi targets -[tasks.pre-test] -condition = { env = { "CARGO_MAKE_CRATE_TARGET_TRIPLE" = "wasm32-wasi" } } -env = { "SKIP_TEST" = true } - [tasks.test] -condition = { env_false = ["SKIP_TEST"] } -dependencies = ["pre-test"] -args = ["test", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"] - -[tasks.post-test] -env = { "SKIP_TEST" = false } +dependencies = ["get-host-triple"] +args = ["test", "--target", "${CARGO_HOST_TRIPLE}", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"] # Running Zellij using the development data directory [tasks.run] @@ -125,6 +116,25 @@ env = { "CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS" = [ ] } run_task = { name = "build", fork = true } +[tasks.get-host-triple] +script_runner = "@duckscript" +script = ''' +output = exec rustc -v -V +lines = split ${output.stdout} \n +triple = set "" +for line in ${lines} + if starts_with ${line} "host:" && not is_empty ${line} + bits = split ${line} " " + triple = array_get ${bits} 1 + triple = set ${triple} + end +end + +if not is_empty ${triple} + set_env CARGO_HOST_TRIPLE "${triple}" +end +''' + [tasks.wasm-opt-plugins] dependencies = ["build-plugins-release"] script_runner = "@duckscript" From e2544bd26a3e836715ffa405f7100e01f5f88161 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 26 Jul 2022 16:02:00 +0200 Subject: [PATCH 90/90] tests/e2e: Add test for custom bindings in the status bar. Makes sure that the modified bindings from a custom configuration file are read and applied to the UI. --- src/tests/e2e/cases.rs | 35 ++++++++++++ src/tests/e2e/remote_runner.rs | 56 +++++++++++++++++++ ...__status_bar_loads_custom_keybindings.snap | 29 ++++++++++ src/tests/fixtures/configs/changed_keys.yaml | 26 +++++++++ 4 files changed, 146 insertions(+) create mode 100644 src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap create mode 100644 src/tests/fixtures/configs/changed_keys.yaml diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 47e7edd44b..7457ed544c 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -983,6 +983,41 @@ pub fn accepts_basic_layout() { assert_snapshot!(last_snapshot); } +#[test] +#[ignore] +pub fn status_bar_loads_custom_keybindings() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let config_file_name = "changed_keys.yaml"; + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + let mut runner = RemoteRunner::new_with_config(fake_win_size, config_file_name); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for app to load", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(3, 1) + && remote_terminal.snapshot_contains("$ █ ││$") + && remote_terminal.snapshot_contains("$ ") { + step_is_complete = true; + } + step_is_complete + }, + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} + #[test] #[ignore] fn focus_pane_with_mouse() { diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index 21c91e5134..03fb619e11 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -20,6 +20,7 @@ use std::rc::Rc; const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/release/zellij"; const SET_ENV_VARIABLES: &str = "EDITOR=/usr/bin/vi"; const ZELLIJ_LAYOUT_PATH: &str = "/usr/src/zellij/fixtures/layouts"; +const ZELLIJ_CONFIG_PATH: &str = "/usr/src/zellij/fixtures/configs"; const ZELLIJ_DATA_DIR: &str = "/usr/src/zellij/e2e-data"; const ZELLIJ_FIXTURE_PATH: &str = "/usr/src/zellij/fixtures"; const CONNECTION_STRING: &str = "127.0.0.1:2222"; @@ -163,6 +164,25 @@ fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) { std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } +fn start_zellij_with_config(channel: &mut ssh2::Channel, config_path: &str) { + stop_zellij(channel); + channel + .write_all( + format!( + "{} {} --config {} --session {} --data-dir {}\n", + SET_ENV_VARIABLES, + ZELLIJ_EXECUTABLE_LOCATION, + config_path, + SESSION_NAME, + ZELLIJ_DATA_DIR + ) + .as_bytes(), + ) + .unwrap(); + channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN +} + fn read_from_channel( channel: &Arc>, last_snapshot: &Arc>, @@ -587,6 +607,42 @@ impl RemoteRunner { reader_thread, } } + pub fn new_with_config(win_size: Size, config_file_name: &'static str) -> Self { + let remote_path = Path::new(ZELLIJ_CONFIG_PATH).join(config_file_name); + let sess = ssh_connect(); + let mut channel = sess.channel_session().unwrap(); + let mut rows = Dimension::fixed(win_size.rows); + let mut cols = Dimension::fixed(win_size.cols); + rows.set_inner(win_size.rows); + cols.set_inner(win_size.cols); + let pane_geom = PaneGeom { + x: 0, + y: 0, + rows, + cols, + }; + setup_remote_environment(&mut channel, win_size); + start_zellij_with_config(&mut channel, &remote_path.to_string_lossy()); + let channel = Arc::new(Mutex::new(channel)); + let last_snapshot = Arc::new(Mutex::new(String::new())); + let cursor_coordinates = Arc::new(Mutex::new((0, 0))); + sess.set_blocking(false); + let reader_thread = + read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom); + RemoteRunner { + steps: vec![], + channel, + currently_running_step: None, + current_step_index: 0, + retries_left: RETRIES, + retry_pause_ms: 100, + test_timed_out: false, + panic_on_no_retries_left: true, + last_snapshot, + cursor_coordinates, + reader_thread, + } + } pub fn dont_panic(mut self) -> Self { self.panic_on_no_retries_left = false; self diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap new file mode 100644 index 0000000000..5a88e98a78 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +assertion_line: 398 +expression: last_snapshot +--- + Zellij (e2e-test)  Tab #1  +┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│$ █ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + LOCK  PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + Tip: UNBOUND => open new pane. UNBOUND => navigate between panes. UNBOUND => increase/decrease pane size. diff --git a/src/tests/fixtures/configs/changed_keys.yaml b/src/tests/fixtures/configs/changed_keys.yaml new file mode 100644 index 0000000000..7d2a8c714d --- /dev/null +++ b/src/tests/fixtures/configs/changed_keys.yaml @@ -0,0 +1,26 @@ +--- +# Configuration for zellij. + +# In order to troubleshoot your configuration try using the following command: +# `zellij setup --check` +# It should show current config locations and features that are enabled. + +keybinds: + unbind: true + normal: + - action: [SwitchToMode: Locked,] + key: [F: 1] + - action: [SwitchToMode: Pane,] + key: [F: 2] + - action: [SwitchToMode: Tab,] + key: [F: 3] + - action: [SwitchToMode: Resize,] + key: [F: 4] + - action: [SwitchToMode: Move,] + key: [F: 5] + - action: [SwitchToMode: Scroll,] + key: [F: 6] + - action: [SwitchToMode: Session,] + key: [F: 7] + - action: [Quit,] + key: [F: 8]