diff --git a/default-tiles/tab-bar/src/line.rs b/default-tiles/tab-bar/src/line.rs new file mode 100644 index 00000000..f28fab3f --- /dev/null +++ b/default-tiles/tab-bar/src/line.rs @@ -0,0 +1,165 @@ +use colored::*; + +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, + tabs_after_active: &mut Vec, + tabs_to_render: &mut Vec, + 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 { + format!(" ← +many ") + }; + let more_styled_text = format!( + "{}{}", + more_text.black().on_yellow(), + ARROW_SEPARATOR.yellow().on_black(), + ); + LinePart { + part: more_styled_text, + len: more_text.chars().count() + 1, // 1 for the arrow + } +} + +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 { + format!(" +many → ") + }; + let more_styled_text = format!( + "{}{}{}", + ARROW_SEPARATOR.black().on_yellow(), + more_text.black().on_yellow(), + ARROW_SEPARATOR.yellow().on_black(), + ); + LinePart { + part: more_styled_text, + len: more_text.chars().count() + 2, // 2 for the arrows + } +} + +fn add_previous_tabs_msg( + tabs_before_active: &mut Vec, + tabs_to_render: &mut Vec, + title_bar: &mut Vec, + cols: usize, +) { + while get_current_title_len(&tabs_to_render) + + // get_tabs_before_len(tabs_before_active.len()) >= cols { + 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, + title_bar: &mut Vec, + cols: usize, +) { + while get_current_title_len(&title_bar) + + // get_tabs_after_len(tabs_after_active.len()) >= cols { + 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); +} + +pub fn tab_line( + mut all_tabs: Vec, + active_tab_index: usize, + cols: usize, +) -> Vec { + let mut tabs_to_render: Vec = 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); + + populate_tabs_in_tab_line( + &mut tabs_before_active, + &mut tabs_after_active, + &mut tabs_to_render, + cols, + ); + + let mut tab_line: Vec = vec![]; + if !tabs_before_active.is_empty() { + add_previous_tabs_msg( + &mut tabs_before_active, + &mut tabs_to_render, + &mut tab_line, + cols, + ); + } + 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); + } + tab_line +} diff --git a/default-tiles/tab-bar/src/main.rs b/default-tiles/tab-bar/src/main.rs index 78a57aca..8a334334 100644 --- a/default-tiles/tab-bar/src/main.rs +++ b/default-tiles/tab-bar/src/main.rs @@ -1,12 +1,25 @@ -use colored::*; +mod line; +mod tab; + use zellij_tile::*; +use crate::line::tab_line; +use crate::tab::nameless_tab; + +#[derive(Debug)] +pub struct LinePart { + part: String, + len: usize, +} + #[derive(Default)] struct State { active_tab_index: usize, num_tabs: usize, } +static ARROW_SEPARATOR: &str = ""; + register_tile!(State); impl ZellijTile for State { @@ -18,19 +31,23 @@ impl ZellijTile for State { self.num_tabs = 0; } - fn draw(&mut self, _rows: usize, _cols: usize) { - let mut s = String::new(); - let active_tab = self.active_tab_index + 1; - for i in 1..=self.num_tabs { - let tab; - if i == active_tab { - tab = format!("*{} ", i).black().bold().on_magenta(); - } else { - tab = format!("-{} ", i).white(); - } - s = format!("{}{}", s, tab); + fn draw(&mut self, _rows: usize, cols: usize) { + if self.num_tabs == 0 { + return; } - println!("Tabs: {}\u{1b}[40m\u{1b}[0K", s); + let mut all_tabs: Vec = vec![]; + for i in 0..self.num_tabs { + let tab = nameless_tab(i, i == self.active_tab_index); + all_tabs.push(tab); + } + + let tab_line = tab_line(all_tabs, self.active_tab_index, cols); + + let mut s = String::new(); + for bar_part in tab_line { + s = format!("{}{}", s, bar_part.part); + } + println!("{}\u{1b}[40m\u{1b}[0K", s); } fn update_tabs(&mut self, active_tab_index: usize, num_tabs: usize) { diff --git a/default-tiles/tab-bar/src/tab.rs b/default-tiles/tab-bar/src/tab.rs new file mode 100644 index 00000000..78a9ba0b --- /dev/null +++ b/default-tiles/tab-bar/src/tab.rs @@ -0,0 +1,52 @@ +use colored::*; + +use crate::{LinePart, ARROW_SEPARATOR}; + +pub fn active_tab(text: String, is_furthest_to_the_left: bool) -> LinePart { + let left_separator = if is_furthest_to_the_left { + " ".black().on_magenta() + } else { + ARROW_SEPARATOR.black().on_magenta() + }; + let right_separator = ARROW_SEPARATOR.magenta().on_black(); + let tab_styled_text = format!("{}{}{}", left_separator, text, right_separator) + .black() + .bold() + .on_magenta(); + let tab_text_len = text.chars().count() + 2; // 2 for left and right separators + LinePart { + part: format!("{}", tab_styled_text), + len: tab_text_len, + } +} + +pub fn non_active_tab(text: String, is_furthest_to_the_left: bool) -> LinePart { + let left_separator = if is_furthest_to_the_left { + " ".black().on_green() + } else { + ARROW_SEPARATOR.black().on_green() + }; + let right_separator = ARROW_SEPARATOR.green().on_black(); + let tab_styled_text = format!("{}{}{}", left_separator, text, right_separator) + .black() + .bold() + .on_green(); + let tab_text_len = text.chars().count() + 2; // 2 for the left and right separators + LinePart { + part: format!("{}", tab_styled_text), + len: tab_text_len, + } +} + +pub fn tab(text: String, is_active_tab: bool, is_furthest_to_the_left: bool) -> LinePart { + if is_active_tab { + active_tab(text, is_furthest_to_the_left) + } else { + non_active_tab(text, is_furthest_to_the_left) + } +} + +pub fn nameless_tab(index: usize, is_active_tab: bool) -> LinePart { + let tab_text = format!(" Tab #{} ", index + 1); + tab(tab_text, is_active_tab, index == 0) +}