summaryrefslogtreecommitdiffstats
path: root/default-plugins
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2022-06-03 11:14:38 +0200
committerGitHub <noreply@github.com>2022-06-03 11:14:38 +0200
commitd62e6fb57e7c35f3b2f165c5a63c809ba21e2c2e (patch)
tree91d3918dc4c044ac71b58d72705c71b57949a5cf /default-plugins
parentad9ba8ab2442f6a5754ecfdb6ad74e1b70dcd762 (diff)
add(plugin): `compact-bar` & `compact` layout (#1450)
* add(plugin): `compact-bar` & `compact` layout * add(nix): `compact-bar` plugin * add(config): `compact-bar` to the config * add(workspace): `compact-bar` to workspace members * add(assets): `compact-bar` * chore(fmt): rustfmt * add(nix): add `compact-bar` * add: compact layout to dump command * nix(build): fix destination of copy command * add(makefile): add `compact-bar` to `plugin-build` * add(layout): `compact-bar` to layout * add: install `compact-bar` plugin * fix(test): update input plugin test * fix(plugin): default colors for compact-bar
Diffstat (limited to 'default-plugins')
-rw-r--r--default-plugins/compact-bar/.cargo/config.toml2
-rw-r--r--default-plugins/compact-bar/Cargo.toml13
l---------default-plugins/compact-bar/LICENSE.md1
-rw-r--r--default-plugins/compact-bar/src/line.rs243
-rw-r--r--default-plugins/compact-bar/src/main.rs137
-rw-r--r--default-plugins/compact-bar/src/tab.rs84
6 files changed, 480 insertions, 0 deletions
diff --git a/default-plugins/compact-bar/.cargo/config.toml b/default-plugins/compact-bar/.cargo/config.toml
new file mode 100644
index 000000000..6b77899cb
--- /dev/null
+++ b/default-plugins/compact-bar/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi"
diff --git a/default-plugins/compact-bar/Cargo.toml b/default-plugins/compact-bar/Cargo.toml
new file mode 100644
index 000000000..6f45e2c40
--- /dev/null
+++ b/default-plugins/compact-bar/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "compact-bar"
+version = "0.1.0"
+authors = ["Alexander Kenji Berthold <aks.kenji@protonmail.com>" ]
+edition = "2021"
+license = "MIT"
+
+[dependencies]
+colored = "2"
+ansi_term = "0.12"
+unicode-width = "0.1.8"
+zellij-tile = { path = "../../zellij-tile" }
+zellij-tile-utils = { path = "../../zellij-tile-utils" }
diff --git a/default-plugins/compact-bar/LICENSE.md b/default-plugins/compact-bar/LICENSE.md
new file mode 120000
index 000000000..f0608a63a
--- /dev/null
+++ b/default-plugins/compact-bar/LICENSE.md
@@ -0,0 +1 @@
+../../LICENSE.md \ No newline at end of file
diff --git a/default-plugins/compact-bar/src/line.rs b/default-plugins/compact-bar/src/line.rs
new file mode 100644
index 000000000..5cd9c6873
--- /dev/null
+++ b/default-plugins/compact-bar/src/line.rs
@@ -0,0 +1,243 @@
+use ansi_term::ANSIStrings;
+use unicode_width::UnicodeWidthStr;
+
+use crate::{LinePart, ARROW_SEPARATOR};
+use zellij_tile::prelude::*;
+use zellij_tile_utils::style;
+
+fn get_current_title_len(current_title: &[LinePart]) -> usize {
+ current_title.iter().map(|p| p.len).sum()
+}
+
+// move elements from before_active and after_active into tabs_to_render while they fit in cols
+// adds collapsed_tabs to the left and right if there's left over tabs that don't fit
+fn populate_tabs_in_tab_line(
+ tabs_before_active: &mut Vec<LinePart>,
+ tabs_after_active: &mut Vec<LinePart>,
+ tabs_to_render: &mut Vec<LinePart>,
+ cols: usize,
+ palette: Palette,
+ capabilities: PluginCapabilities,
+) {
+ let mut middle_size = get_current_title_len(tabs_to_render);
+
+ let mut total_left = 0;
+ let mut total_right = 0;
+ loop {
+ let left_count = tabs_before_active.len();
+ let right_count = tabs_after_active.len();
+ let collapsed_left = left_more_message(left_count, palette, tab_separator(capabilities));
+ let collapsed_right = right_more_message(right_count, palette, tab_separator(capabilities));
+
+ let total_size = collapsed_left.len + middle_size + collapsed_right.len;
+
+ if total_size > cols {
+ // break and dont add collapsed tabs to tabs_to_render, they will not fit
+ break;
+ }
+
+ let left = if let Some(tab) = tabs_before_active.last() {
+ tab.len
+ } else {
+ usize::MAX
+ };
+
+ let right = if let Some(tab) = tabs_after_active.first() {
+ tab.len
+ } else {
+ usize::MAX
+ };
+
+ // total size is shortened if the next tab to be added is the last one, as that will remove the collapsed tab
+ let size_by_adding_left =
+ left.saturating_add(total_size)
+ .saturating_sub(if left_count == 1 {
+ collapsed_left.len
+ } else {
+ 0
+ });
+ let size_by_adding_right =
+ right
+ .saturating_add(total_size)
+ .saturating_sub(if right_count == 1 {
+ collapsed_right.len
+ } else {
+ 0
+ });
+
+ let left_fits = size_by_adding_left <= cols;
+ let right_fits = size_by_adding_right <= cols;
+ // active tab is kept in the middle by adding to the side that
+ // has less width, or if the tab on the other side doesn' fit
+ if (total_left <= total_right || !right_fits) && left_fits {
+ // add left tab
+ let tab = tabs_before_active.pop().unwrap();
+ middle_size += tab.len;
+ total_left += tab.len;
+ tabs_to_render.insert(0, tab);
+ } else if right_fits {
+ // add right tab
+ let tab = tabs_after_active.remove(0);
+ middle_size += tab.len;
+ total_right += tab.len;
+ tabs_to_render.push(tab);
+ } else {
+ // there's either no space to add more tabs or no more tabs to add, so we're done
+ tabs_to_render.insert(0, collapsed_left);
+ tabs_to_render.push(collapsed_right);
+ break;
+ }
+ }
+}
+
+fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator: &str) -> LinePart {
+ if tab_count_to_the_left == 0 {
+ return LinePart::default();
+ }
+ let more_text = if tab_count_to_the_left < 10000 {
+ format!(" ← +{} ", tab_count_to_the_left)
+ } else {
+ " ← +many ".to_string()
+ };
+ // 238
+ // chars length plus separator length on both sides
+ let more_text_len = more_text.width() + 2 * separator.width();
+ let text_color = match palette.theme_hue {
+ ThemeHue::Dark => palette.white,
+ ThemeHue::Light => palette.black,
+ };
+ let left_separator = style!(text_color, palette.orange).paint(separator);
+ let more_styled_text = style!(text_color, palette.orange).bold().paint(more_text);
+ let right_separator = style!(palette.orange, text_color).paint(separator);
+ let more_styled_text =
+ ANSIStrings(&[left_separator, more_styled_text, right_separator]).to_string();
+ LinePart {
+ part: more_styled_text,
+ len: more_text_len,
+ }
+}
+
+fn right_more_message(
+ tab_count_to_the_right: usize,
+ palette: Palette,
+ separator: &str,
+) -> LinePart {
+ if tab_count_to_the_right == 0 {
+ return LinePart::default();
+ };
+ let more_text = if tab_count_to_the_right < 10000 {
+ format!(" +{} → ", tab_count_to_the_right)
+ } else {
+ " +many → ".to_string()
+ };
+ // chars length plus separator length on both sides
+ let more_text_len = more_text.width() + 2 * separator.width();
+ let text_color = match palette.theme_hue {
+ ThemeHue::Dark => palette.white,
+ ThemeHue::Light => palette.black,
+ };
+ let left_separator = style!(text_color, palette.orange).paint(separator);
+ let more_styled_text = style!(text_color, palette.orange).bold().paint(more_text);
+ let right_separator = style!(palette.orange, text_color).paint(separator);
+ let more_styled_text =
+ ANSIStrings(&[left_separator, more_styled_text, right_separator]).to_string();
+ LinePart {
+ part: more_styled_text,
+ len: more_text_len,
+ }
+}
+
+fn tab_line_prefix(
+ session_name: Option<&str>,
+ mode: InputMode,
+ palette: Palette,
+ cols: usize,
+) -> Vec<LinePart> {
+ let prefix_text = " Zellij ".to_string();
+
+ let prefix_text_len = prefix_text.chars().count();
+ let text_color = match palette.theme_hue {
+ ThemeHue::Dark => palette.white,
+ ThemeHue::Light => palette.black,
+ };
+ let bg_color = match palette.theme_hue {
+ ThemeHue::Dark => palette.black,
+ ThemeHue::Light => palette.white,
+ };
+ let prefix_styled_text = style!(text_color, bg_color).bold().paint(prefix_text);
+ let mut parts = vec![LinePart {
+ part: prefix_styled_text.to_string(),
+ len: prefix_text_len,
+ }];
+ if let Some(name) = session_name {
+ let name_part = format!("({}) ", name);
+ let name_part_len = name_part.width();
+ let text_color = match palette.theme_hue {
+ ThemeHue::Dark => palette.white,
+ ThemeHue::Light => palette.black,
+ };
+ let name_part_styled_text = style!(text_color, bg_color).bold().paint(name_part);
+ if cols.saturating_sub(prefix_text_len) >= name_part_len {
+ parts.push(LinePart {
+ part: name_part_styled_text.to_string(),
+ len: name_part_len,
+ })
+ }
+ }
+ let mode_part = format!("({:?})", mode);
+ let mode_part_len = mode_part.width();
+ let mode_part_styled_text = style!(text_color, bg_color).bold().paint(mode_part);
+ if cols.saturating_sub(prefix_text_len) >= mode_part_len {
+ parts.push(LinePart {
+ part: format!("({:^6})", mode_part_styled_text),
+ len: mode_part_len,
+ })
+ }
+ parts
+}
+
+pub fn tab_separator(capabilities: PluginCapabilities) -> &'static str {
+ if !capabilities.arrow_fonts {
+ ARROW_SEPARATOR
+ } else {
+ ""
+ }
+}
+
+pub fn tab_line(
+ session_name: Option<&str>,
+ mut all_tabs: Vec<LinePart>,
+ active_tab_index: usize,
+ cols: usize,
+ palette: Palette,
+ capabilities: PluginCapabilities,
+ mode: InputMode,
+) -> Vec<LinePart> {
+ let mut tabs_after_active = all_tabs.split_off(active_tab_index);
+ let mut tabs_before_active = all_tabs;
+ let active_tab = if !tabs_after_active.is_empty() {
+ tabs_after_active.remove(0)
+ } else {
+ tabs_before_active.pop().unwrap()
+ };
+ let mut prefix = tab_line_prefix(session_name, mode, palette, cols);
+ let prefix_len = get_current_title_len(&prefix);
+
+ // if active tab alone won't fit in cols, don't draw any tabs
+ if prefix_len + active_tab.len > cols {
+ return prefix;
+ }
+
+ let mut tabs_to_render = vec![active_tab];
+
+ populate_tabs_in_tab_line(
+ &mut tabs_before_active,
+ &mut tabs_after_active,
+ &mut tabs_to_render,
+ cols.saturating_sub(prefix_len),
+ palette,
+ capabilities,
+ );
+ prefix.append(&mut tabs_to_render);
+ prefix
+}
diff --git a/default-plugins/compact-bar/src/main.rs b/default-plugins/compact-bar/src/main.rs
new file mode 100644
index 000000000..3e5e4a184
--- /dev/null
+++ b/default-plugins/compact-bar/src/main.rs
@@ -0,0 +1,137 @@
+mod line;
+mod tab;
+
+use std::cmp::{max, min};
+use std::convert::TryInto;
+
+use zellij_tile::prelude::*;
+
+use crate::line::tab_line;
+use crate::tab::tab_style;
+
+#[derive(Debug, Default)]
+pub struct LinePart {
+ part: String,
+ len: usize,
+}
+
+#[derive(Default)]
+struct State {
+ tabs: Vec<TabInfo>,
+ active_tab_idx: usize,
+ mode_info: ModeInfo,
+ mouse_click_pos: usize,
+ should_render: bool,
+}
+
+static ARROW_SEPARATOR: &str = "";
+
+register_plugin!(State);
+
+impl ZellijPlugin for State {
+ fn load(&mut self) {
+ set_selectable(false);
+ subscribe(&[
+ EventType::TabUpdate,
+ EventType::ModeUpdate,
+ EventType::Mouse,
+ ]);
+ }
+
+ fn update(&mut self, event: Event) {
+ match event {
+ Event::ModeUpdate(mode_info) => self.mode_info = mode_info,
+ Event::TabUpdate(tabs) => {
+ if let Some(active_tab_index) = tabs.iter().position(|t| t.active) {
+ // tabs are indexed starting from 1 so we need to add 1
+ self.active_tab_idx = active_tab_index + 1;
+ self.tabs = tabs;
+ } else {
+ eprintln!("Could not find active tab.");
+ }
+ }
+ Event::Mouse(me) => match me {
+ Mouse::LeftClick(_, col) => {
+ self.mouse_click_pos = col;
+ self.should_render = true;
+ }
+ Mouse::ScrollUp(_) => {
+ switch_tab_to(min(self.active_tab_idx + 1, self.tabs.len()) as u32);
+ }
+ Mouse::ScrollDown(_) => {
+ switch_tab_to(max(self.active_tab_idx.saturating_sub(1), 1) as u32);
+ }
+ _ => {}
+ },
+ _ => {
+ eprintln!("Got unrecognized event: {:?}", event);
+ }
+ }
+ }
+
+ fn render(&mut self, _rows: usize, cols: usize) {
+ if self.tabs.is_empty() {
+ return;
+ }
+ let mut all_tabs: Vec<LinePart> = vec![];
+ let mut active_tab_index = 0;
+ for t in &mut self.tabs {
+ let mut tabname = t.name.clone();
+ if t.active && self.mode_info.mode == InputMode::RenameTab {
+ if tabname.is_empty() {
+ tabname = String::from("Enter name...");
+ }
+ active_tab_index = t.position;
+ } else if t.active {
+ active_tab_index = t.position;
+ }
+ let tab = tab_style(
+ tabname,
+ t.active,
+ t.is_sync_panes_active,
+ self.mode_info.style.colors,
+ self.mode_info.capabilities,
+ t.other_focused_clients.as_slice(),
+ );
+ all_tabs.push(tab);
+ }
+ let tab_line = tab_line(
+ self.mode_info.session_name.as_deref(),
+ all_tabs,
+ active_tab_index,
+ cols.saturating_sub(1),
+ self.mode_info.style.colors,
+ self.mode_info.capabilities,
+ self.mode_info.mode,
+ );
+ let mut s = String::new();
+ let mut len_cnt = 0;
+ for (idx, bar_part) in tab_line.iter().enumerate() {
+ s = format!("{}{}", s, &bar_part.part);
+
+ if self.should_render
+ && self.mouse_click_pos > len_cnt
+ && self.mouse_click_pos <= len_cnt + bar_part.len
+ && idx > 2
+ {
+ // First three elements of tab_line are "Zellij", session name and empty thing, hence the idx > 2 condition.
+ // Tabs are indexed starting from 1, therefore we need subtract 2 below.
+ switch_tab_to(TryInto::<u32>::try_into(idx).unwrap() - 2);
+ }
+ len_cnt += bar_part.len;
+ }
+ let background = match self.mode_info.style.colors.theme_hue {
+ ThemeHue::Dark => self.mode_info.style.colors.black,
+ ThemeHue::Light => self.mode_info.style.colors.white,
+ };
+ match background {
+ PaletteColor::Rgb((r, g, b)) => {
+ println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
+ }
+ PaletteColor::EightBit(color) => {
+ println!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
+ }
+ }
+ self.should_render = false;
+ }
+}
diff --git a/default-plugins/compact-bar/src/tab.rs b/default-plugins/compact-bar/src/tab.rs
new file mode 100644
index 000000000..993489620
--- /dev/null
+++ b/default-plugins/compact-bar/src/tab.rs
@@ -0,0 +1,84 @@
+use crate::{line::tab_separator, LinePart};
+use ansi_term::{ANSIString, ANSIStrings};
+use unicode_width::UnicodeWidthStr;
+use zellij_tile::prelude::*;
+use zellij_tile_utils::style;
+
+fn cursors(focused_clients: &[ClientId], palette: Palette) -> (Vec<ANSIString>, usize) {
+ // cursor section, text length
+ let mut len = 0;
+ let mut cursors = vec![];
+ for client_id in focused_clients.iter() {
+ if let Some(color) = client_id_to_colors(*client_id, palette) {
+ cursors.push(style!(color.1, color.0).paint(" "));
+ len += 1;
+ }
+ }
+ (cursors, len)
+}
+
+pub fn render_tab(
+ text: String,
+ palette: Palette,
+ separator: &str,
+ focused_clients: &[ClientId],
+ active: bool,
+) -> LinePart {
+ let background_color = if active { palette.green } else { palette.fg };
+ let foreground_color = match palette.theme_hue {
+ ThemeHue::Dark => palette.black,
+ ThemeHue::Light => palette.white,
+ };
+ let left_separator = style!(foreground_color, background_color).paint(separator);
+ let mut tab_text_len = text.width() + 2 + separator.width() * 2; // 2 for left and right separators, 2 for the text padding
+
+ let tab_styled_text = style!(foreground_color, background_color)
+ .bold()
+ .paint(format!(" {} ", text));
+
+ 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;
+ let mut s = String::new();
+ let cursor_beginning = style!(foreground_color, background_color)
+ .bold()
+ .paint("[")
+ .to_string();
+ let cursor_section = ANSIStrings(&cursor_section).to_string();
+ let cursor_end = style!(foreground_color, background_color)
+ .bold()
+ .paint("]")
+ .to_string();
+ s.push_str(&left_separator.to_string());
+ s.push_str(&tab_styled_text.to_string());
+ s.push_str(&cursor_beginning);
+ s.push_str(&cursor_section);
+ s.push_str(&cursor_end);
+ s.push_str(&right_separator.to_string());
+ s
+ } else {
+ ANSIStrings(&[left_separator, tab_styled_text, right_separator]).to_string()
+ };
+
+ LinePart {
+ part: tab_styled_text,
+ len: tab_text_len,
+ }
+}
+
+pub fn tab_style(
+ text: String,
+ is_active_tab: bool,
+ is_sync_panes_active: bool,
+ palette: Palette,
+ capabilities: PluginCapabilities,
+ focused_clients: &[ClientId],
+) -> LinePart {
+ let separator = tab_separator(capabilities);
+ let mut tab_text = text;
+ if is_sync_panes_active {
+ tab_text.push_str(" (Sync)");
+ }
+ render_tab(tab_text, palette, separator, focused_clients, is_active_tab)
+}