summaryrefslogtreecommitdiffstats
path: root/default-plugins/tab-bar
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-07-05 15:13:51 +0200
committerGitHub <noreply@github.com>2024-07-05 15:13:51 +0200
commita6d6c0e4ff03c35cafb6f04363ecab856ee3682d (patch)
treeeadf1c24c4c6a1e4b7499b3cd047351fd29e52de /default-plugins/tab-bar
parent8e33b20559ecddcaba17e5cc8d6f05b638718cac (diff)
feat(ui): status bar redesign (#3475)
* work * work * working * get default mode from server and some ui responsiveness * work * finish design and get tests to pass * get e2e tests to pass * add classic layout * add classic layout assets * fix e2e tests * style(fmt): rustfmt * fix plugin system test * style(fmt): some cleanups
Diffstat (limited to 'default-plugins/tab-bar')
-rw-r--r--default-plugins/tab-bar/src/line.rs211
-rw-r--r--default-plugins/tab-bar/src/main.rs17
-rw-r--r--default-plugins/tab-bar/src/tab.rs2
3 files changed, 224 insertions, 6 deletions
diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs
index 668507a01..349981f4d 100644
--- a/default-plugins/tab-bar/src/line.rs
+++ b/default-plugins/tab-bar/src/line.rs
@@ -1,9 +1,14 @@
use ansi_term::ANSIStrings;
+use ansi_term::{
+ Color::{Fixed, RGB},
+ Style,
+};
use unicode_width::UnicodeWidthStr;
use crate::{LinePart, ARROW_SEPARATOR};
+use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
-use zellij_tile_utils::style;
+use zellij_tile_utils::{palette_match, style};
fn get_current_title_len(current_title: &[LinePart]) -> usize {
current_title.iter().map(|p| p.len).sum()
@@ -224,6 +229,9 @@ pub fn tab_line(
palette: Palette,
capabilities: PluginCapabilities,
hide_session_name: bool,
+ tab_info: Option<&TabInfo>,
+ mode_info: &ModeInfo,
+ hide_swap_layout_indicator: bool,
) -> Vec<LinePart> {
let mut tabs_after_active = all_tabs.split_off(active_tab_index);
let mut tabs_before_active = all_tabs;
@@ -236,10 +244,26 @@ pub fn tab_line(
true => tab_line_prefix(None, palette, cols),
false => tab_line_prefix(session_name, palette, cols),
};
- let prefix_len = get_current_title_len(&prefix);
+
+ let mut swap_layout_indicator = if hide_swap_layout_indicator {
+ None
+ } else {
+ tab_info.and_then(|tab_info| {
+ swap_layout_status(
+ cols,
+ &tab_info.active_swap_layout_name,
+ tab_info.is_swap_layout_dirty,
+ mode_info,
+ !capabilities.arrow_fonts,
+ )
+ })
+ };
+
+ let non_tab_len =
+ get_current_title_len(&prefix) + swap_layout_indicator.as_ref().map(|s| s.len).unwrap_or(0);
// if active tab alone won't fit in cols, don't draw any tabs
- if prefix_len + active_tab.len > cols {
+ if non_tab_len + active_tab.len > cols {
return prefix;
}
@@ -249,10 +273,189 @@ pub fn tab_line(
&mut tabs_before_active,
&mut tabs_after_active,
&mut tabs_to_render,
- cols.saturating_sub(prefix_len),
+ cols.saturating_sub(non_tab_len),
palette,
capabilities,
);
prefix.append(&mut tabs_to_render);
+
+ if let Some(mut swap_layout_indicator) = swap_layout_indicator.take() {
+ let remaining_space = cols
+ .saturating_sub(prefix.iter().fold(0, |len, part| len + part.len))
+ .saturating_sub(swap_layout_indicator.len);
+ let mut padding = String::new();
+ let mut padding_len = 0;
+ for _ in 0..remaining_space {
+ padding.push_str(" ");
+ padding_len += 1;
+ }
+ swap_layout_indicator.part = format!("{}{}", padding, swap_layout_indicator.part);
+ swap_layout_indicator.len += padding_len;
+ prefix.push(swap_layout_indicator);
+ }
+
prefix
}
+
+fn swap_layout_status(
+ cols: usize,
+ swap_layout_name: &Option<String>,
+ is_swap_layout_dirty: bool,
+ mode_info: &ModeInfo,
+ supports_arrow_fonts: bool,
+) -> Option<LinePart> {
+ match swap_layout_name {
+ Some(swap_layout_name) => {
+ let mode_keybinds = mode_info.get_mode_keybinds();
+ let prev_next_keys = action_key_group(
+ &mode_keybinds,
+ &[&[Action::PreviousSwapLayout], &[Action::NextSwapLayout]],
+ );
+ let mut text = style_key_with_modifier(&prev_next_keys, Some(0));
+ text.append(&ribbon_as_line_part(
+ &swap_layout_name.to_uppercase(),
+ !is_swap_layout_dirty,
+ supports_arrow_fonts,
+ ));
+ Some(text)
+ },
+ None => None,
+ }
+}
+
+pub fn ribbon_as_line_part(text: &str, is_selected: bool, supports_arrow_fonts: bool) -> LinePart {
+ let ribbon_text = if is_selected {
+ Text::new(text).selected()
+ } else {
+ Text::new(text)
+ };
+ let part = serialize_ribbon(&ribbon_text);
+ let mut len = text.width() + 2;
+ if supports_arrow_fonts {
+ len += 2;
+ };
+ LinePart {
+ part,
+ len,
+ tab_index: None,
+ }
+}
+
+pub fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option<usize>) -> LinePart {
+ if keyvec.is_empty() {
+ return LinePart::default();
+ }
+
+ let common_modifiers = get_common_modifiers(keyvec.iter().collect());
+
+ let no_common_modifier = common_modifiers.is_empty();
+ let modifier_str = common_modifiers
+ .iter()
+ .map(|m| m.to_string())
+ .collect::<Vec<_>>()
+ .join("-");
+
+ // Prints the keys
+ let key = keyvec
+ .iter()
+ .map(|key| {
+ if no_common_modifier || keyvec.len() == 1 {
+ format!("{}", key)
+ } else {
+ format!("{}", key.strip_common_modifiers(&common_modifiers))
+ }
+ })
+ .collect::<Vec<String>>();
+
+ // Special handling of some pre-defined keygroups
+ let key_string = key.join("");
+ let key_separator = match &key_string[..] {
+ "HJKL" => "",
+ "hjkl" => "",
+ "←↓↑→" => "",
+ "←→" => "",
+ "↓↑" => "",
+ "[]" => "",
+ _ => "|",
+ };
+
+ if no_common_modifier || key.len() == 1 {
+ let key_string_text = format!(" {} ", key.join(key_separator));
+ let text = if let Some(color_index) = color_index {
+ Text::new(&key_string_text).color_range(color_index, ..)
+ } else {
+ Text::new(&key_string_text)
+ };
+ LinePart {
+ part: serialize_text(&text),
+ len: key_string_text.width(),
+ ..Default::default()
+ }
+ } else {
+ let key_string_without_modifier = format!("{}", key.join(key_separator));
+ let key_string_text = format!(" {} <{}> ", modifier_str, key_string_without_modifier);
+ let text = if let Some(color_index) = color_index {
+ Text::new(&key_string_text)
+ .color_range(color_index, ..modifier_str.width() + 1)
+ .color_range(
+ color_index,
+ modifier_str.width() + 3
+ ..modifier_str.width() + 3 + key_string_without_modifier.width(),
+ )
+ } else {
+ Text::new(&key_string_text)
+ };
+ LinePart {
+ part: serialize_text(&text),
+ len: key_string_text.width(),
+ ..Default::default()
+ }
+ }
+}
+
+pub fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec<KeyModifier> {
+ if keyvec.is_empty() {
+ return vec![];
+ }
+ let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone();
+ for key in keyvec {
+ common_modifiers = common_modifiers
+ .intersection(&key.key_modifiers)
+ .cloned()
+ .collect();
+ }
+ common_modifiers.into_iter().collect()
+}
+
+pub fn action_key_group(
+ keymap: &[(KeyWithModifier, Vec<Action>)],
+ actions: &[&[Action]],
+) -> Vec<KeyWithModifier> {
+ let mut ret = vec![];
+ for action in actions {
+ ret.extend(action_key(keymap, action));
+ }
+ ret
+}
+
+pub fn action_key(
+ keymap: &[(KeyWithModifier, Vec<Action>)],
+ action: &[Action],
+) -> Vec<KeyWithModifier> {
+ keymap
+ .iter()
+ .filter_map(|(key, acvec)| {
+ let matching = acvec
+ .iter()
+ .zip(action)
+ .filter(|(a, b)| a.shallow_eq(b))
+ .count();
+
+ if matching == acvec.len() && matching == action.len() {
+ Some(key.clone())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<KeyWithModifier>>()
+}
diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs
index 208558251..8230467d1 100644
--- a/default-plugins/tab-bar/src/main.rs
+++ b/default-plugins/tab-bar/src/main.rs
@@ -18,12 +18,20 @@ pub struct LinePart {
tab_index: Option<usize>,
}
+impl LinePart {
+ pub fn append(&mut self, to_append: &LinePart) {
+ self.part.push_str(&to_append.part);
+ self.len += to_append.len;
+ }
+}
+
#[derive(Default)]
struct State {
tabs: Vec<TabInfo>,
active_tab_idx: usize,
mode_info: ModeInfo,
tab_line: Vec<LinePart>,
+ hide_swap_layout_indication: bool,
}
static ARROW_SEPARATOR: &str = "";
@@ -31,7 +39,11 @@ static ARROW_SEPARATOR: &str = "";
register_plugin!(State);
impl ZellijPlugin for State {
- fn load(&mut self, _configuration: BTreeMap<String, String>) {
+ fn load(&mut self, configuration: BTreeMap<String, String>) {
+ self.hide_swap_layout_indication = configuration
+ .get("hide_swap_layout_indication")
+ .map(|s| s == "true")
+ .unwrap_or(false);
set_selectable(false);
subscribe(&[
EventType::TabUpdate,
@@ -120,6 +132,9 @@ impl ZellijPlugin for State {
self.mode_info.style.colors,
self.mode_info.capabilities,
self.mode_info.style.hide_session_name,
+ self.tabs.iter().find(|t| t.active),
+ &self.mode_info,
+ self.hide_swap_layout_indication,
);
let output = self
diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs
index 4b2ae7776..5044ac872 100644
--- a/default-plugins/tab-bar/src/tab.rs
+++ b/default-plugins/tab-bar/src/tab.rs
@@ -51,7 +51,7 @@ pub fn render_tab(
let right_separator = style!(background_color, foreground_color).paint(separator);
let tab_styled_text = if !focused_clients.is_empty() {
let (cursor_section, extra_length) = cursors(focused_clients, palette);
- tab_text_len += extra_length;
+ tab_text_len += extra_length + 2; // 2 for cursor_beginning and cursor_end
let mut s = String::new();
let cursor_beginning = style!(foreground_color, background_color)
.bold()