summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-07-12 11:31:00 +0200
committerGitHub <noreply@github.com>2023-07-12 11:31:00 +0200
commit27763d26ab83dd9f81c69c75601cbf6075f13790 (patch)
tree7dfaf7aff3edfc3f97a79b22f247abf2ae1b6c77
parent61f3789c88ca5c948cacaa2d1228e51389cc24f5 (diff)
feat(ui): new status bar mode (#2619)
* supermode prototype * fix integration tests * fix tests * style(fmt): rustfmt
-rw-r--r--default-plugins/status-bar/src/first_line.rs367
-rw-r--r--default-plugins/status-bar/src/main.rs81
-rw-r--r--default-plugins/status-bar/src/second_line.rs140
-rw-r--r--default-plugins/status-bar/src/tip/data/compact_layout.rs2
-rw-r--r--default-plugins/status-bar/src/tip/data/edit_scrollbuffer.rs2
-rw-r--r--default-plugins/status-bar/src/tip/data/floating_panes_mouse.rs2
-rw-r--r--default-plugins/status-bar/src/tip/data/quicknav.rs2
-rw-r--r--default-plugins/status-bar/src/tip/data/sync_tab.rs2
-rw-r--r--src/tests/e2e/cases.rs2
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap4
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap4
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap4
12 files changed, 439 insertions, 173 deletions
diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs
index 23a6d5869..183aa64f9 100644
--- a/default-plugins/status-bar/src/first_line.rs
+++ b/default-plugins/status-bar/src/first_line.rs
@@ -8,13 +8,14 @@ use crate::{
};
use crate::{ColoredElements, LinePart};
+#[derive(Debug, Clone, Copy)]
struct KeyShortcut {
mode: KeyMode,
- action: KeyAction,
+ pub action: KeyAction,
key: Option<Key>,
}
-#[derive(PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
enum KeyAction {
Lock,
Pane,
@@ -27,6 +28,7 @@ enum KeyAction {
Tmux,
}
+#[derive(Debug, Clone, Copy)]
enum KeyMode {
Unselected,
UnselectedAlternate,
@@ -34,6 +36,20 @@ enum KeyMode {
Disabled,
}
+fn letter_shortcut(key: &Key, with_prefix: bool) -> String {
+ if with_prefix {
+ format!("{}", key)
+ } else {
+ match key {
+ Key::F(c) => format!("{}", c),
+ Key::Ctrl(c) => format!("{}", c),
+ Key::Char(_) => format!("{}", key),
+ Key::Alt(c) => format!("{}", c),
+ _ => String::from("??"),
+ }
+ }
+}
+
impl KeyShortcut {
pub fn new(mode: KeyMode, action: KeyAction, key: Option<Key>) -> Self {
KeyShortcut { mode, action, key }
@@ -57,17 +73,7 @@ impl KeyShortcut {
Some(k) => k,
None => return String::from("?"),
};
- if with_prefix {
- format!("{}", key)
- } else {
- match key {
- Key::F(c) => format!("{}", c),
- Key::Ctrl(c) => format!("{}", c),
- Key::Char(_) => format!("{}", key),
- Key::Alt(c) => format!("{}", c),
- _ => String::from("??"),
- }
- }
+ letter_shortcut(&key, with_prefix)
}
}
@@ -196,6 +202,58 @@ fn short_mode_shortcut(
}
}
+fn short_key_indicators(
+ max_len: usize,
+ keys: &[KeyShortcut],
+ palette: ColoredElements,
+ separator: &str,
+ mode_info: &ModeInfo,
+ no_super: bool,
+) -> LinePart {
+ let mut line_part = if no_super {
+ LinePart::default()
+ } else {
+ superkey(palette, separator, mode_info)
+ };
+ let shared_super = if no_super { true } else { line_part.len > 0 };
+ for ctrl_key in keys {
+ let line_empty = line_part.len == 0;
+ 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;
+ }
+ if line_part.len < max_len {
+ return line_part;
+ }
+
+ // Shortened doesn't fit, print nothing
+ line_part = LinePart::default();
+ line_part
+}
+
+fn full_key_indicators(
+ keys: &[KeyShortcut],
+ palette: ColoredElements,
+ separator: &str,
+ mode_info: &ModeInfo,
+ no_super: bool,
+) -> LinePart {
+ // Print full-width hints
+ let mut line_part = if no_super {
+ LinePart::default()
+ } else {
+ superkey(palette, separator, mode_info)
+ };
+ let shared_super = if no_super { true } else { line_part.len > 0 };
+ for ctrl_key in keys {
+ let line_empty = line_part.len == 0;
+ 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;
+ }
+ line_part
+}
+
fn key_indicators(
max_len: usize,
keys: &[KeyShortcut],
@@ -402,6 +460,111 @@ pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo)
}
}
+fn standby_mode_shortcut_key(help: &ModeInfo) -> Key {
+ let binds = &help.get_mode_keybinds();
+ match help.mode {
+ InputMode::Locked => to_char(action_key(
+ binds,
+ &[Action::SwitchToMode(InputMode::Normal)],
+ )),
+ _ => to_char(action_key(
+ binds,
+ &[Action::SwitchToMode(InputMode::Locked)],
+ )),
+ }
+ .unwrap_or(Key::Char('?'))
+}
+
+fn standby_mode_ui_indication(
+ has_shared_super: bool,
+ standby_mode: &InputMode,
+ standby_mode_shortcut_key: Key,
+ colored_elements: ColoredElements,
+ separator: &str,
+) -> LinePart {
+ let mut line_part = LinePart::default();
+ let standby_mode_shortcut = standby_mode_single_letter_selected(
+ has_shared_super,
+ standby_mode_shortcut_key,
+ colored_elements,
+ separator,
+ );
+ // standby mode text hint
+ let key_shortcut = KeyShortcut::new(
+ KeyMode::Unselected,
+ input_mode_to_key_action(&standby_mode),
+ None,
+ );
+ let styled_text = colored_elements
+ .unselected
+ .styled_text
+ .paint(format!(" {} ", key_shortcut.full_text()));
+ let suffix_separator = colored_elements
+ .unselected
+ .suffix_separator
+ .paint(separator);
+ let standby_mode_text = LinePart {
+ part: ANSIStrings(&[styled_text, suffix_separator]).to_string(),
+ len: key_shortcut.full_text().chars().count() + separator.chars().count() + 2, // 2 padding
+ };
+ line_part.part = format!("{}{}", line_part.part, standby_mode_shortcut.part);
+ line_part.len += standby_mode_shortcut.len;
+ line_part.part = format!("{}{}", line_part.part, standby_mode_text.part);
+ line_part.len += standby_mode_text.len;
+ line_part
+}
+
+fn standby_mode_single_letter_unselected(
+ has_shared_super: bool,
+ shortcut_key: Key,
+ palette: ColoredElements,
+ separator: &str,
+) -> LinePart {
+ let prefix_separator = palette.unselected.prefix_separator.paint(separator);
+ let char_shortcut = palette.unselected.char_shortcut.paint(format!(
+ " {} ",
+ letter_shortcut(&shortcut_key, has_shared_super)
+ ));
+ let suffix_separator = palette.unselected.suffix_separator.paint(separator);
+ let len = prefix_separator.chars().count()
+ + char_shortcut.chars().count()
+ + suffix_separator.chars().count();
+ LinePart {
+ part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
+ len,
+ }
+}
+
+fn standby_mode_single_letter_selected(
+ has_shared_super: bool,
+ shortcut_key: Key,
+ palette: ColoredElements,
+ separator: &str,
+) -> LinePart {
+ let prefix_separator = palette
+ .selected_standby_shortcut
+ .prefix_separator
+ .paint(separator);
+ let char_shortcut = palette
+ .selected_standby_shortcut
+ .char_shortcut
+ .paint(format!(
+ " {} ",
+ letter_shortcut(&shortcut_key, has_shared_super)
+ ));
+ let suffix_separator = palette
+ .selected_standby_shortcut
+ .suffix_separator
+ .paint(separator);
+ let len = prefix_separator.chars().count()
+ + char_shortcut.chars().count()
+ + suffix_separator.chars().count();
+ LinePart {
+ part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
+ len,
+ }
+}
+
pub fn to_char(kv: Vec<Key>) -> Option<Key> {
let key = kv
.iter()
@@ -419,6 +582,19 @@ pub fn to_char(kv: Vec<Key>) -> Option<Key> {
key.cloned()
}
+fn input_mode_to_key_action(input_mode: &InputMode) -> KeyAction {
+ match input_mode {
+ InputMode::Normal | InputMode::Prompt | InputMode::Tmux => KeyAction::Lock,
+ 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,
+ }
+}
+
/// Get the [`KeyShortcut`] for a specific [`InputMode`].
///
/// Iterates over the contents of `shortcuts` to find the [`KeyShortcut`] with the [`KeyAction`]
@@ -449,7 +625,8 @@ fn get_key_shortcut_for_mode<'a>(
None
}
-pub fn first_line(
+pub fn first_line_supermode(
+ standby_mode: &InputMode,
help: &ModeInfo,
tab_info: Option<&TabInfo>,
max_len: usize,
@@ -457,9 +634,129 @@ pub fn first_line(
) -> LinePart {
let supports_arrow_fonts = !help.capabilities.arrow_fonts;
let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts);
+
+ let standby_mode_shortcut_key = standby_mode_shortcut_key(&help);
+
+ let mut line = superkey(colored_elements, separator, help);
+ let has_shared_super = line.len == 0;
+ let max_len_without_superkey = max_len.saturating_sub(line.len);
+ let mut append_to_line = |line_part_to_append: LinePart| {
+ line.part = format!("{}{}", line.part, line_part_to_append.part);
+ line.len += line_part_to_append.len;
+ };
+ match help.mode {
+ InputMode::Locked => {
+ let standby_mode_shortcut = standby_mode_single_letter_unselected(
+ has_shared_super,
+ standby_mode_shortcut_key,
+ colored_elements,
+ separator,
+ );
+ append_to_line(standby_mode_shortcut);
+ line
+ },
+ _ => {
+ let mut default_keys = generate_default_keys(help);
+ default_keys.remove(0); // remove locked mode which is not relevant to the supermode ui
+
+ if let Some(position) = default_keys
+ .iter()
+ .position(|d| d.action == input_mode_to_key_action(standby_mode))
+ {
+ let standby_mode_ui_ribbon = standby_mode_ui_indication(
+ has_shared_super,
+ &standby_mode,
+ standby_mode_shortcut_key,
+ colored_elements,
+ separator,
+ );
+ let first_keybinds: Vec<KeyShortcut> =
+ default_keys.iter().cloned().take(position).collect();
+ let second_keybinds: Vec<KeyShortcut> =
+ default_keys.iter().cloned().skip(position + 1).collect();
+ let first_key_indicators =
+ full_key_indicators(&first_keybinds, colored_elements, separator, help, true);
+ let second_key_indicators =
+ full_key_indicators(&second_keybinds, colored_elements, separator, help, true);
+
+ if first_key_indicators.len + standby_mode_ui_ribbon.len + second_key_indicators.len
+ < max_len_without_superkey
+ {
+ append_to_line(first_key_indicators);
+ append_to_line(standby_mode_ui_ribbon);
+ append_to_line(second_key_indicators);
+ } else {
+ let length_of_each_half =
+ max_len_without_superkey.saturating_sub(standby_mode_ui_ribbon.len) / 2;
+ let first_key_indicators = short_key_indicators(
+ length_of_each_half,
+ &first_keybinds,
+ colored_elements,
+ separator,
+ help,
+ true,
+ );
+ let second_key_indicators = short_key_indicators(
+ length_of_each_half,
+ &second_keybinds,
+ colored_elements,
+ separator,
+ help,
+ true,
+ );
+ append_to_line(first_key_indicators);
+ append_to_line(standby_mode_ui_ribbon);
+ append_to_line(second_key_indicators);
+ }
+ if line.len < max_len {
+ if let Some(tab_info) = tab_info {
+ let remaining_space = max_len.saturating_sub(line.len);
+ line.append(&swap_layout_status_and_padding(
+ &tab_info,
+ remaining_space,
+ separator,
+ colored_elements,
+ help,
+ ));
+ }
+ }
+ }
+ line
+ },
+ }
+}
+
+fn swap_layout_status_and_padding(
+ tab_info: &TabInfo,
+ mut remaining_space: usize,
+ separator: &str,
+ colored_elements: ColoredElements,
+ help: &ModeInfo,
+) -> LinePart {
+ let mut line = LinePart::default();
+ if let Some(swap_layout_status) = swap_layout_status(
+ remaining_space,
+ &tab_info.active_swap_layout_name,
+ tab_info.is_swap_layout_dirty,
+ help,
+ colored_elements,
+ &help.style.colors,
+ separator,
+ ) {
+ remaining_space -= swap_layout_status.len;
+ for _ in 0..remaining_space {
+ line.part
+ .push_str(&ANSIStrings(&[colored_elements.superkey_prefix.paint(" ")]).to_string());
+ line.len += 1;
+ }
+ line.append(&swap_layout_status);
+ }
+ line
+}
+
+fn generate_default_keys(help: &ModeInfo) -> Vec<KeyShortcut> {
let binds = &help.get_mode_keybinds();
- // Unselect all by default
- let mut default_keys = vec![
+ vec![
KeyShortcut::new(
KeyMode::Unselected,
KeyAction::Lock,
@@ -512,7 +809,20 @@ pub fn first_line(
KeyAction::Quit,
to_char(action_key(binds, &[Action::Quit])),
),
- ];
+ ]
+}
+
+pub fn first_line(
+ help: &ModeInfo,
+ tab_info: Option<&TabInfo>,
+ 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();
+ // Unselect all by default
+ let mut default_keys = generate_default_keys(help); // TODO: check that this still works
if let Some(key_shortcut) = get_key_shortcut_for_mode(&mut default_keys, &help.mode) {
key_shortcut.mode = KeyMode::Selected;
@@ -539,25 +849,14 @@ pub fn first_line(
key_indicators(max_len, &default_keys, colored_elements, separator, help);
if key_indicators.len < max_len {
if let Some(tab_info) = tab_info {
- let mut remaining_space = max_len - key_indicators.len;
- if let Some(swap_layout_status) = swap_layout_status(
+ let remaining_space = max_len - key_indicators.len;
+ key_indicators.append(&swap_layout_status_and_padding(
+ &tab_info,
remaining_space,
- &tab_info.active_swap_layout_name,
- tab_info.is_swap_layout_dirty,
- help,
- colored_elements,
- &help.style.colors,
separator,
- ) {
- remaining_space -= swap_layout_status.len;
- for _ in 0..remaining_space {
- key_indicators.part.push_str(
- &ANSIStrings(&[colored_elements.superkey_prefix.paint(" ")]).to_string(),
- );
- key_indicators.len += 1;
- }
- key_indicators.append(&swap_layout_status);
- }
+ colored_elements,
+ help,
+ ));
}
}
key_indicators
diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs
index 3e6685596..ee14e45d5 100644
--- a/default-plugins/status-bar/src/main.rs
+++ b/default-plugins/status-bar/src/main.rs
@@ -13,10 +13,9 @@ use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
use zellij_tile_utils::{palette_match, style};
-use first_line::first_line;
+use first_line::{first_line, first_line_supermode};
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,
+ floating_panes_are_visible, fullscreen_panes_to_hide, keybinds, system_clipboard_error,
text_copied_hint,
};
use tip::utils::get_cached_tip_name;
@@ -34,6 +33,10 @@ struct State {
mode_info: ModeInfo,
text_copy_destination: Option<CopyDestination>,
display_system_clipboard_failure: bool,
+
+ supermode: bool,
+ standby_mode: InputMode,
+ current_mode: InputMode,
}
register_plugin!(State);
@@ -60,6 +63,7 @@ impl Display for LinePart {
#[derive(Clone, Copy)]
pub struct ColoredElements {
pub selected: SegmentStyle,
+ pub selected_standby_shortcut: SegmentStyle,
pub unselected: SegmentStyle,
pub unselected_alternate: SegmentStyle,
pub disabled: SegmentStyle,
@@ -109,6 +113,14 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored
styled_text: style!(background, palette.green).bold(),
suffix_separator: style!(palette.green, background).bold(),
},
+ selected_standby_shortcut: 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, palette.fg).bold(),
+ },
unselected: SegmentStyle {
prefix_separator: style!(background, palette.fg),
char_left_separator: style!(background, palette.fg).bold(),
@@ -145,6 +157,14 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored
styled_text: style!(background, palette.green).bold(),
suffix_separator: style!(palette.green, background).bold(),
},
+ selected_standby_shortcut: 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, palette.fg).bold(),
+ },
unselected: SegmentStyle {
prefix_separator: style!(background, palette.fg),
char_left_separator: style!(background, palette.fg).bold(),
@@ -187,12 +207,44 @@ impl ZellijPlugin for State {
EventType::InputReceived,
EventType::SystemClipboardFailure,
]);
+ self.supermode = false; // TODO: from config
+ self.standby_mode = InputMode::Pane;
+ if self.supermode {
+ switch_to_input_mode(&InputMode::Locked); // supermode should start locked (TODO: only
+ // once per app load, let's not do this on
+ // each tab loading)
+ }
}
fn update(&mut self, event: Event) -> bool {
let mut should_render = false;
match event {
Event::ModeUpdate(mode_info) => {
+ if self.supermode {
+ // supermode is a "sticky" mode that is not Normal or Locked
+ // using this configuration, we default to Locked mode in order to avoid key
+ // collisions with terminal applications
+ // whenever the user switches away from locked mode, we make sure to place them
+ // in the standby mode
+ // whenever the user switches to a mode that is not locked or normal, we set
+ // that mode as the standby mode
+ // whenever the user switches away from the standby mode, we palce them in
+ // normal mode
+ if mode_info.mode == InputMode::Normal && self.current_mode == InputMode::Locked
+ {
+ switch_to_input_mode(&self.standby_mode);
+ } else if mode_info.mode == InputMode::Normal
+ && self.current_mode == self.standby_mode
+ {
+ switch_to_input_mode(&InputMode::Locked);
+ } else if mode_info.mode != InputMode::Locked
+ && mode_info.mode != InputMode::Normal
+ {
+ self.standby_mode = mode_info.mode;
+ }
+ self.current_mode = mode_info.mode;
+ }
+
if self.mode_info != mode_info {
should_render = true;
}
@@ -244,7 +296,17 @@ impl ZellijPlugin for State {
};
let active_tab = self.tabs.iter().find(|t| t.active);
- let first_line = first_line(&self.mode_info, active_tab, cols, separator);
+ let first_line = if self.supermode {
+ first_line_supermode(
+ &self.standby_mode,
+ &self.mode_info,
+ active_tab,
+ cols,
+ separator,
+ )
+ } else {
+ first_line(&self.mode_info, active_tab, cols, separator)
+ };
let second_line = self.second_line(cols);
let background = match self.mode_info.style.colors.theme_hue {
@@ -296,11 +358,7 @@ impl State {
} else if let Some(active_tab) = active_tab {
if active_tab.is_fullscreen_active {
match self.mode_info.mode {
- InputMode::Normal => fullscreen_panes_to_hide(
- &self.mode_info.style.colors,
- active_tab.panes_to_hide,
- ),
- InputMode::Locked => locked_fullscreen_panes_to_hide(
+ InputMode::Normal | InputMode::Locked => fullscreen_panes_to_hide(
&self.mode_info.style.colors,
active_tab.panes_to_hide,
),
@@ -308,9 +366,8 @@ impl State {
}
} else if active_tab.are_floating_panes_visible {
match self.mode_info.mode {
- InputMode::Normal => floating_panes_are_visible(&self.mode_info),
- InputMode::Locked => {
- locked_floating_panes_are_visible(&self.mode_info.style.colors)
+ InputMode::Normal | InputMode::Locked => {
+ floating_panes_are_visible(&self.mode_info)
},
_ => keybinds(&self.mode_info, &self.tip_name, cols),
}
diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs
index b7c8a9cec..015618f06 100644
--- a/default-plugins/status-bar/src/second_line.rs
+++ b/default-plugins/status-bar/src/second_line.rs
@@ -45,20 +45,6 @@ fn full_length_shortcut(
}
}
-fn locked_interface_indication(palette: Palette) -> LinePart {
- let locked_text = " -- INTERFACE LOCKED -- ";
- let locked_text_len = locked_text.chars().count();
- let text_color = palette_match!(match palette.theme_hue {
- ThemeHue::Dark => palette.white,
- ThemeHue::Light => palette.black,
- });
- let locked_styled_text = Style::new().fg(text_color).bold().paint(locked_text);
- LinePart {
- part: locked_styled_text.to_string(),
- len: locked_text_len,
- }
-}
-
fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec<Key>) -> LinePart {
let shortcut = if linepart.len == 0 {
full_length_shortcut(true, keys, text, help.style.colors)
@@ -144,21 +130,17 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
}
if mi.mode == IM::Pane { vec![
- (s("Move focus"), s("Move"),
+ (s("New"), s("New"), action_key(&km, &[A::NewPane(None, None), TO_NORMAL])),
+ (s("Change Focus"), s("Move"),
action_key_group(&km, &[&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)],</