summaryrefslogtreecommitdiffstats
path: root/default-plugins/tab-bar
diff options
context:
space:
mode:
authordenis <denismaximov98@gmail.com>2021-04-22 11:45:29 +0300
committerdenis <denismaximov98@gmail.com>2021-04-22 11:45:29 +0300
commit8d98ca7da0bc278a832e4e7de23ff63a84deb529 (patch)
treee7fdfaa6c1ae25e7737dbb36bd1dbcb94d62ab47 /default-plugins/tab-bar
parentb2139f4f3472b0cae18a106479e8a1ef92f7115e (diff)
parent80e1f2927007347d540107b2aa8de7ffac7751b5 (diff)
wip: merge main in
Diffstat (limited to 'default-plugins/tab-bar')
-rw-r--r--default-plugins/tab-bar/.cargo/config.toml2
-rw-r--r--default-plugins/tab-bar/Cargo.toml11
l---------default-plugins/tab-bar/LICENSE.md1
-rw-r--r--default-plugins/tab-bar/src/line.rs180
-rw-r--r--default-plugins/tab-bar/src/main.rs84
-rw-r--r--default-plugins/tab-bar/src/tab.rs67
6 files changed, 345 insertions, 0 deletions
diff --git a/default-plugins/tab-bar/.cargo/config.toml b/default-plugins/tab-bar/.cargo/config.toml
new file mode 100644
index 000000000..6b77899cb
--- /dev/null
+++ b/default-plugins/tab-bar/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi"
diff --git a/default-plugins/tab-bar/Cargo.toml b/default-plugins/tab-bar/Cargo.toml
new file mode 100644
index 000000000..59b95b4bf
--- /dev/null
+++ b/default-plugins/tab-bar/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "tab-bar"
+version = "0.1.0"
+authors = ["Jonah Caplan <jonahcaplan@gmail.com>"]
+edition = "2018"
+license = "MIT"
+
+[dependencies]
+colored = "2"
+ansi_term = "0.12"
+zellij-tile = { path = "../../zellij-tile" }
diff --git a/default-plugins/tab-bar/LICENSE.md b/default-plugins/tab-bar/LICENSE.md
new file mode 120000
index 000000000..f0608a63a
--- /dev/null
+++ b/default-plugins/tab-bar/LICENSE.md
@@ -0,0 +1 @@
+../../LICENSE.md \ No newline at end of file
diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs
new file mode 100644
index 000000000..1d1c33ecd
--- /dev/null
+++ b/default-plugins/tab-bar/src/line.rs
@@ -0,0 +1,180 @@
+use crate::colors::{BLACK, GRAY, ORANGE, WHITE};
+use ansi_term::{ANSIStrings, Style};
+
+use crate::{LinePart, ARROW_SEPARATOR};
+
+fn get_current_title_len(current_title: &[LinePart]) -> usize {
+ current_title
+ .iter()
+ .fold(0, |acc, title_part| acc + title_part.len)
+}
+
+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,
+) {
+ let mut take_next_tab_from_tabs_after = true;
+ loop {
+ if tabs_before_active.is_empty() && tabs_after_active.is_empty() {
+ break;
+ }
+ let current_title_len = get_current_title_len(&tabs_to_render);
+ if current_title_len >= cols {
+ break;
+ }
+ let should_take_next_tab = take_next_tab_from_tabs_after;
+ let can_take_next_tab = !tabs_after_active.is_empty()
+ && tabs_after_active.get(0).unwrap().len + current_title_len <= cols;
+ let can_take_previous_tab = !tabs_before_active.is_empty()
+ && tabs_before_active.last().unwrap().len + current_title_len <= cols;
+ if should_take_next_tab && can_take_next_tab {
+ let next_tab = tabs_after_active.remove(0);
+ tabs_to_render.push(next_tab);
+ take_next_tab_from_tabs_after = false;
+ } else if can_take_previous_tab {
+ let previous_tab = tabs_before_active.pop().unwrap();
+ tabs_to_render.insert(0, previous_tab);
+ take_next_tab_from_tabs_after = true;
+ } else if can_take_next_tab {
+ let next_tab = tabs_after_active.remove(0);
+ tabs_to_render.push(next_tab);
+ take_next_tab_from_tabs_after = false;
+ } else {
+ break;
+ }
+ }
+}
+
+fn left_more_message(tab_count_to_the_left: usize) -> LinePart {
+ if tab_count_to_the_left == 0 {
+ return LinePart {
+ part: String::new(),
+ len: 0,
+ };
+ }
+ let more_text = if tab_count_to_the_left < 10000 {
+ format!(" ← +{} ", tab_count_to_the_left)
+ } else {
+ " ← +many ".to_string()
+ };
+ // 238
+ let more_text_len = more_text.chars().count() + 2; // 2 for the arrows
+ let left_separator = Style::new().fg(GRAY).on(ORANGE).paint(ARROW_SEPARATOR);
+ let more_styled_text = Style::new().fg(BLACK).on(ORANGE).bold().paint(more_text);
+ let right_separator = Style::new().fg(ORANGE).on(GRAY).paint(ARROW_SEPARATOR);
+ let more_styled_text = format!(
+ "{}",
+ ANSIStrings(&[left_separator, more_styled_text, right_separator,])
+ );
+ LinePart {
+ part: more_styled_text,
+ len: more_text_len,
+ }
+}
+
+fn right_more_message(tab_count_to_the_right: usize) -> LinePart {
+ if tab_count_to_the_right == 0 {
+ return LinePart {
+ part: String::new(),
+ len: 0,
+ };
+ };
+ let more_text = if tab_count_to_the_right < 10000 {
+ format!(" +{} → ", tab_count_to_the_right)
+ } else {
+ " +many → ".to_string()
+ };
+ let more_text_len = more_text.chars().count() + 1; // 2 for the arrow
+ let left_separator = Style::new().fg(GRAY).on(ORANGE).paint(ARROW_SEPARATOR);
+ let more_styled_text = Style::new().fg(BLACK).on(ORANGE).bold().paint(more_text);
+ let right_separator = Style::new().fg(ORANGE).on(GRAY).paint(ARROW_SEPARATOR);
+ let more_styled_text = format!(
+ "{}",
+ ANSIStrings(&[left_separator, more_styled_text, right_separator,])
+ );
+ LinePart {
+ part: more_styled_text,
+ len: more_text_len,
+ }
+}
+
+fn add_previous_tabs_msg(
+ tabs_before_active: &mut Vec<LinePart>,
+ tabs_to_render: &mut Vec<LinePart>,
+ title_bar: &mut Vec<LinePart>,
+ cols: usize,
+) {
+ while get_current_title_len(&tabs_to_render) + left_more_message(tabs_before_active.len()).len
+ >= cols
+ {
+ tabs_before_active.push(tabs_to_render.remove(0));
+ }
+ let left_more_message = left_more_message(tabs_before_active.len());
+ title_bar.push(left_more_message);
+}
+
+fn add_next_tabs_msg(
+ tabs_after_active: &mut Vec<LinePart>,
+ title_bar: &mut Vec<LinePart>,
+ cols: usize,
+) {
+ while get_current_title_len(&title_bar) + right_more_message(tabs_after_active.len()).len
+ >= cols
+ {
+ tabs_after_active.insert(0, title_bar.pop().unwrap());
+ }
+ let right_more_message = right_more_message(tabs_after_active.len());
+ title_bar.push(right_more_message);
+}
+
+fn tab_line_prefix() -> LinePart {
+ let prefix_text = " Zellij ".to_string();
+ let prefix_text_len = prefix_text.chars().count();
+ let prefix_styled_text = Style::new().fg(WHITE).on(GRAY).bold().paint(prefix_text);
+ LinePart {
+ part: format!("{}", prefix_styled_text),
+ len: prefix_text_len,
+ }
+}
+
+pub fn tab_line(
+ mut all_tabs: Vec<LinePart>,
+ active_tab_index: usize,
+ cols: usize,
+) -> Vec<LinePart> {
+ let mut tabs_to_render: Vec<LinePart> = vec![];
+ 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()
+ };
+ tabs_to_render.push(active_tab);
+
+ let prefix = tab_line_prefix();
+ populate_tabs_in_tab_line(
+ &mut tabs_before_active,
+ &mut tabs_after_active,
+ &mut tabs_to_render,
+ cols - prefix.len,
+ );
+
+ let mut tab_line: Vec<LinePart> = vec![];
+ if !tabs_before_active.is_empty() {
+ add_previous_tabs_msg(
+ &mut tabs_before_active,
+ &mut tabs_to_render,
+ &mut tab_line,
+ cols - prefix.len,
+ );
+ }
+ tab_line.append(&mut tabs_to_render);
+ if !tabs_after_active.is_empty() {
+ add_next_tabs_msg(&mut tabs_after_active, &mut tab_line, cols - prefix.len);
+ }
+ tab_line.insert(0, prefix);
+ tab_line
+}
diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs
new file mode 100644
index 000000000..e4a84d987
--- /dev/null
+++ b/default-plugins/tab-bar/src/main.rs
@@ -0,0 +1,84 @@
+mod line;
+mod tab;
+
+use zellij_tile::prelude::*;
+
+use crate::line::tab_line;
+use crate::tab::tab_style;
+
+#[derive(Debug)]
+pub struct LinePart {
+ part: String,
+ len: usize,
+}
+
+#[derive(Default)]
+struct State {
+ tabs: Vec<TabInfo>,
+ mode_info: ModeInfo,
+}
+
+static ARROW_SEPARATOR: &str = "";
+
+pub mod colors {
+ use ansi_term::Colour::{self, Fixed};
+ pub const WHITE: Colour = Fixed(255);
+ pub const BLACK: Colour = Fixed(16);
+ pub const GREEN: Colour = Fixed(154);
+ pub const ORANGE: Colour = Fixed(166);
+ pub const GRAY: Colour = Fixed(238);
+ pub const BRIGHT_GRAY: Colour = Fixed(245);
+ pub const RED: Colour = Fixed(88);
+}
+
+register_plugin!(State);
+
+impl ZellijPlugin for State {
+ fn load(&mut self) {
+ set_selectable(false);
+ set_invisible_borders(true);
+ set_max_height(1);
+ subscribe(&[EventType::TabUpdate, EventType::ModeUpdate]);
+ }
+
+ fn update(&mut self, event: Event) {
+ match event {
+ Event::ModeUpdate(mode_info) => self.mode_info.mode = mode_info.mode,
+ Event::TabUpdate(tabs) => self.tabs = tabs,
+ _ => unimplemented!(), // FIXME: This should be unreachable, but this could be cleaner
+ }
+ }
+
+ 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 self.tabs.iter_mut() {
+ 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.position, self.mode_info.palette);
+ all_tabs.push(tab);
+ }
+ let tab_line = tab_line(all_tabs, active_tab_index, cols);
+ let mut s = String::new();
+ for bar_part in tab_line {
+ s = format!("{}{}", s, bar_part.part);
+ }
+ println!(
+ "{}\u{1b}[48;2;{};{};{}m\u{1b}[0K",
+ s,
+ self.mode_info.palette.fg.0,
+ self.mode_info.palette.fg.1,
+ self.mode_info.palette.fg.2
+ );
+ }
+}
diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs
new file mode 100644
index 000000000..375d8c462
--- /dev/null
+++ b/default-plugins/tab-bar/src/tab.rs
@@ -0,0 +1,67 @@
+use crate::colors::{BLACK, BRIGHT_GRAY, GRAY, GREEN};
+use crate::{LinePart, ARROW_SEPARATOR};
+use ansi_term::{ANSIStrings, Color::RGB, Style};
+use zellij_tile::data::Palette;
+
+pub fn active_tab(text: String, palette: Palette) -> LinePart {
+ let left_separator = Style::new()
+ .fg(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .paint(ARROW_SEPARATOR);
+ let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the text padding
+ let tab_styled_text = Style::new()
+ .fg(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .bold()
+ .paint(format!(" {} ", text));
+ let right_separator = Style::new()
+ .fg(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .paint(ARROW_SEPARATOR);
+ let tab_styled_text = format!(
+ "{}",
+ ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
+ );
+ LinePart {
+ part: tab_styled_text,
+ len: tab_text_len,
+ }
+}
+
+pub fn non_active_tab(text: String, palette: Palette) -> LinePart {
+ let left_separator = Style::new()
+ .fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .paint(ARROW_SEPARATOR);
+ let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the padding
+ let tab_styled_text = Style::new()
+ .fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .bold()
+ .paint(format!(" {} ", text));
+ let right_separator = Style::new()
+ .fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .paint(ARROW_SEPARATOR);
+ let tab_styled_text = format!(
+ "{}",
+ ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
+ );
+ LinePart {
+ part: tab_styled_text,
+ len: tab_text_len,
+ }
+}
+
+pub fn tab_style(text: String, is_active_tab: bool, position: usize, palette: Palette) -> LinePart {
+ let tab_text = if text.is_empty() {
+ format!("Tab #{}", position + 1)
+ } else {
+ text
+ };
+ if is_active_tab {
+ active_tab(tab_text, palette)
+ } else {
+ non_active_tab(tab_text, palette)
+ }
+}