aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins
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
parentwip: fixing the first arrow (diff)
parentfix(cargo): reduce crate size by 4.8MB using `cargo diet`, to 77kB (diff)
downloadsl0ppij-8d98ca7da0bc278a832e4e7de23ff63a84deb529.tar.gz
sl0ppij-8d98ca7da0bc278a832e4e7de23ff63a84deb529.tar.xz
sl0ppij-8d98ca7da0bc278a832e4e7de23ff63a84deb529.zip
wip: merge main in
Diffstat (limited to 'default-plugins')
-rw-r--r--default-plugins/status-bar/.cargo/config.toml2
-rw-r--r--default-plugins/status-bar/Cargo.toml11
l---------default-plugins/status-bar/LICENSE.md1
-rw-r--r--default-plugins/status-bar/src/first_line.rs395
-rw-r--r--default-plugins/status-bar/src/main.rs76
-rw-r--r--default-plugins/status-bar/src/second_line.rs365
-rw-r--r--default-plugins/strider/.cargo/config.toml2
-rw-r--r--default-plugins/strider/Cargo.toml12
l---------default-plugins/strider/LICENSE.md1
-rw-r--r--default-plugins/strider/src/main.rs97
-rw-r--r--default-plugins/strider/src/state.rs63
-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
17 files changed, 1370 insertions, 0 deletions
diff --git a/default-plugins/status-bar/.cargo/config.toml b/default-plugins/status-bar/.cargo/config.toml
new file mode 100644
index 00000000..bc255e30
--- /dev/null
+++ b/default-plugins/status-bar/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi" \ No newline at end of file
diff --git a/default-plugins/status-bar/Cargo.toml b/default-plugins/status-bar/Cargo.toml
new file mode 100644
index 00000000..c94d8feb
--- /dev/null
+++ b/default-plugins/status-bar/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "status-bar"
+version = "0.1.0"
+authors = ["Brooks J Rady <b.j.rady@gmail.com>"]
+edition = "2018"
+license = "MIT"
+
+[dependencies]
+colored = "2"
+ansi_term = "0.12"
+zellij-tile = { path = "../../zellij-tile" }
diff --git a/default-plugins/status-bar/LICENSE.md b/default-plugins/status-bar/LICENSE.md
new file mode 120000
index 00000000..f0608a63
--- /dev/null
+++ b/default-plugins/status-bar/LICENSE.md
@@ -0,0 +1 @@
+../../LICENSE.md \ No newline at end of file
diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs
new file mode 100644
index 00000000..4053210d
--- /dev/null
+++ b/default-plugins/status-bar/src/first_line.rs
@@ -0,0 +1,395 @@
+use ansi_term::{ANSIStrings, Color::RGB, Style};
+use zellij_tile::prelude::*;
+
+use crate::colors::{BLACK, BRIGHT_GRAY, GRAY, GREEN, RED, WHITE};
+use crate::{LinePart, ARROW_SEPARATOR};
+
+struct CtrlKeyShortcut {
+ mode: CtrlKeyMode,
+ action: CtrlKeyAction,
+}
+
+impl CtrlKeyShortcut {
+ pub fn new(mode: CtrlKeyMode, action: CtrlKeyAction) -> Self {
+ CtrlKeyShortcut { mode, action }
+ }
+}
+
+enum CtrlKeyAction {
+ Lock,
+ Pane,
+ Tab,
+ Resize,
+ Scroll,
+ Quit,
+}
+
+enum CtrlKeyMode {
+ Unselected,
+ Selected,
+ Disabled,
+}
+
+impl CtrlKeyShortcut {
+ pub fn full_text(&self) -> String {
+ match self.action {
+ CtrlKeyAction::Lock => String::from("LOCK"),
+ CtrlKeyAction::Pane => String::from("PANE"),
+ CtrlKeyAction::Tab => String::from("TAB"),
+ CtrlKeyAction::Resize => String::from("RESIZE"),
+ CtrlKeyAction::Scroll => String::from("SCROLL"),
+ CtrlKeyAction::Quit => String::from("QUIT"),
+ }
+ }
+ pub fn shortened_text(&self) -> String {
+ match self.action {
+ CtrlKeyAction::Lock => String::from("LOCK"),
+ CtrlKeyAction::Pane => String::from("ane"),
+ CtrlKeyAction::Tab => String::from("ab"),
+ CtrlKeyAction::Resize => String::from("esize"),
+ CtrlKeyAction::Scroll => String::from("croll"),
+ CtrlKeyAction::Quit => String::from("uit"),
+ }
+ }
+ pub fn letter_shortcut(&self) -> char {
+ match self.action {
+ CtrlKeyAction::Lock => 'g',
+ CtrlKeyAction::Pane => 'p',
+ CtrlKeyAction::Tab => 't',
+ CtrlKeyAction::Resize => 'r',
+ CtrlKeyAction::Scroll => 's',
+ CtrlKeyAction::Quit => 'q',
+ }
+ }
+}
+
+fn unselected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePart {
+ let prefix_separator = Style::new()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .paint(ARROW_SEPARATOR);
+ let char_left_separator = Style::new()
+ .bold()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .bold()
+ .paint(" <");
+ let char_shortcut = Style::new()
+ .bold()
+ .fg(RGB(palette.red.0, palette.red.1, palette.red.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .bold()
+ .paint(letter.to_string());
+ let char_right_separator = Style::new()
+ .bold()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .bold()
+ .paint(">");
+ let styled_text = Style::new()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .bold()
+ .paint(format!("{} ", text));
+ let suffix_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);
+ LinePart {
+ part: ANSIStrings(&[
+ prefix_separator,
+ char_left_separator,
+ char_shortcut,
+ char_right_separator,
+ styled_text,
+ suffix_separator,
+ ])
+ .to_string(),
+ len: text.chars().count() + 7, // 2 for the arrows, 3 for the char separators, 1 for the character, 1 for the text padding
+ }
+}
+
+fn selected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePart {
+ let prefix_separator = Style::new()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .paint(ARROW_SEPARATOR);
+ let char_left_separator = Style::new()
+ .fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .bold()
+ .paint(format!(" <"));
+ let char_shortcut = Style::new()
+ .bold()
+ .fg(RGB(palette.red.0, palette.red.1, palette.red.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .bold()
+ .paint(format!("{}", letter));
+ let char_right_separator = Style::new()
+ .fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .bold()
+ .paint(format!(">"));
+ let styled_text = Style::new()
+ .fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .bold()
+ .paint(format!("{} ", text));
+ let suffix_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);
+ LinePart {
+ part: ANSIStrings(&[
+ prefix_separator,
+ char_left_separator,
+ char_shortcut,
+ char_right_separator,
+ styled_text,
+ suffix_separator,
+ ])
+ .to_string(),
+ len: text.chars().count() + 7, // 2 for the arrows, 3 for the char separators, 1 for the character, 1 for the text padding
+ }
+}
+
+fn disabled_mode_shortcut(text: &str, palette: Palette) -> LinePart {
+ let prefix_separator = Style::new()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .paint(ARROW_SEPARATOR);
+ let styled_text = Style::new()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .dimmed()
+ .paint(format!("{} ", text));
+ let suffix_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);
+ LinePart {
+ part: format!("{}{}{}", prefix_separator, styled_text, suffix_separator),
+ len: text.chars().count() + 2 + 1, // 2 for the arrows, 1 for the padding in the end
+ }
+}
+
+fn selected_mode_shortcut_single_letter(letter: char, palette: Palette) -> LinePart {
+ let char_shortcut_text = format!(" {} ", letter);
+ let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
+ let prefix_separator = Style::new()
+ .fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .paint(ARROW_SEPARATOR);
+ let char_shortcut = Style::new()
+ .bold()
+ .fg(RGB(palette.red.0, palette.red.1, palette.red.2))
+ .on(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .bold()
+ .paint(char_shortcut_text);
+ let suffix_separator = Style::new()
+ .fg(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .paint(ARROW_SEPARATOR);
+ LinePart {
+ part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
+ len,
+ }
+}
+
+fn unselected_mode_shortcut_single_letter(letter: char, palette: Palette) -> LinePart {
+ let char_shortcut_text = format!(" {} ", letter);
+ let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
+ let prefix_separator = Style::new()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .paint(ARROW_SEPARATOR);
+ let char_shortcut = Style::new()
+ .bold()
+ .fg(RGB(palette.red.0, palette.red.1, palette.red.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .bold()
+ .paint(char_shortcut_text);
+ let suffix_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);
+ LinePart {
+ part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
+ len,
+ }
+}
+
+fn full_ctrl_key(key: &CtrlKeyShortcut, palette: Palette) -> LinePart {
+ let full_text = key.full_text();
+ let letter_shortcut = key.letter_shortcut();
+ match key.mode {
+ CtrlKeyMode::Unselected => {
+ unselected_mode_shortcut(letter_shortcut, &format!(" {}", full_text), palette)
+ }
+ CtrlKeyMode::Selected => {
+ selected_mode_shortcut(letter_shortcut, &format!(" {}", full_text), palette)
+ }
+ CtrlKeyMode::Disabled => {
+ disabled_mode_shortcut(&format!(" <{}> {}", letter_shortcut, full_text), palette)
+ }
+ }
+}
+
+fn shortened_ctrl_key(key: &CtrlKeyShortcut, palette: Palette) -> LinePart {
+ let shortened_text = key.shortened_text();
+ let letter_shortcut = key.letter_shortcut();
+ let shortened_text = match key.action {
+ CtrlKeyAction::Lock => format!(" {}", shortened_text),
+ _ => shortened_text,
+ };
+ match key.mode {
+ CtrlKeyMode::Unselected => {
+ unselected_mode_shortcut(letter_shortcut, &shortened_text, palette)
+ }
+ CtrlKeyMode::Selected => selected_mode_shortcut(letter_shortcut, &shortened_text, palette),
+ CtrlKeyMode::Disabled => disabled_mode_shortcut(
+ &format!(" <{}>{}", letter_shortcut, shortened_text),
+ palette,
+ ),
+ CtrlKeyMode::Disabled => disabled_mode_shortcut(
+ &format!(" <{}>{}", letter_shortcut, shortened_text),
+ palette,
+ ),
+ }
+}
+
+fn single_letter_ctrl_key(key: &CtrlKeyShortcut, palette: Palette) -> LinePart {
+ let letter_shortcut = key.letter_shortcut();
+ match key.mode {
+ CtrlKeyMode::Unselected => unselected_mode_shortcut_single_letter(letter_shortcut, palette),
+ CtrlKeyMode::Selected => selected_mode_shortcut_single_letter(letter_shortcut, palette),
+ CtrlKeyMode::Disabled => disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette),
+ }
+}
+
+fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: Palette) -> LinePart {
+ let mut line_part = LinePart::default();
+ for ctrl_key in keys {
+ let key = full_ctrl_key(ctrl_key, palette);
+ line_part.part = format!("{}{}", line_part.part, key.part);
+ line_part.len += key.len;
+ }
+ if line_part.len < max_len {
+ return line_part;
+ }
+ line_part = LinePart::default();
+ for ctrl_key in keys {
+ let key = shortened_ctrl_key(ctrl_key, palette);
+ line_part.part = format!("{}{}", line_part.part, key.part);
+ line_part.len += key.len;
+ }
+ if line_part.len < max_len {
+ return line_part;
+ }
+ line_part = LinePart::default();
+ for ctrl_key in keys {
+ let key = single_letter_ctrl_key(ctrl_key, palette);
+ line_part.part = format!("{}{}", line_part.part, key.part);
+ line_part.len += key.len;
+ }
+ if line_part.len < max_len {
+ return line_part;
+ }
+ line_part = LinePart::default();
+ line_part
+}
+
+pub fn superkey(palette: Palette) -> LinePart {
+ let prefix_text = " Ctrl + ";
+ let prefix = Style::new()
+ .fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .on(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
+ .bold()
+ .paint(prefix_text);
+
+ let suffix_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);
+ LinePart {
+ part: ANSIStrings(&[prefix, suffix_separator]).to_string(),
+ len: prefix_text.chars().count(),
+ }
+}
+
+pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
+ match &help.mode {
+ InputMode::Locked => key_indicators(
+ max_len,
+ &[
+ CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Lock),
+ CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Pane),
+ CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab),
+ CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize),
+ CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll),
+ CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit),
+ ],
+ help.palette,
+ ),
+ InputMode::Resize => key_indicators(
+ max_len,
+ &[
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab),
+ CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
+ ],
+ help.palette,
+ ),
+ InputMode::Pane => key_indicators(
+ max_len,
+ &[
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock),
+ CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Pane),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
+ ],
+ help.palette,
+ ),
+ InputMode::Tab | InputMode::RenameTab => key_indicators(
+ max_len,
+ &[
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane),
+ CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Tab),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
+ ],
+ help.palette,
+ ),
+ InputMode::Scroll => key_indicators(
+ max_len,
+ &[
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
+ CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
+ ],
+ help.palette,
+ ),
+ InputMode::Normal => key_indicators(
+ max_len,
+ &[
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
+ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
+ ],
+ help.palette,
+ ),
+ }
+}
diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs
new file mode 100644
index 00000000..10322c52
--- /dev/null
+++ b/default-plugins/status-bar/src/main.rs
@@ -0,0 +1,76 @@
+mod first_line;
+mod second_line;
+
+use std::fmt::{Display, Error, Formatter};
+use zellij_tile::{data::Theme, prelude::*};
+
+use first_line::{ctrl_keys, superkey};
+use second_line::keybinds;
+
+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);
+}
+
+// for more of these, copy paste from: https://en.wikipedia.org/wiki/Box-drawing_character
+static ARROW_SEPARATOR: &str = "";
+static MORE_MSG: &str = " ... ";
+
+#[derive(Default)]
+struct State {
+ mode_info: ModeInfo,
+}
+
+register_plugin!(State);
+
+#[derive(Default)]
+pub struct LinePart {
+ part: String,
+ len: usize,
+}
+
+impl Display for LinePart {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ write!(f, "{}", self.part)
+ }
+}
+
+impl ZellijPlugin for State {
+ fn load(&mut self) {
+ set_selectable(false);
+ set_invisible_borders(true);
+ set_max_height(2);
+ subscribe(&[EventType::ModeUpdate]);
+ }
+
+ fn update(&mut self, event: Event) {
+ if let Event::ModeUpdate(mode_info) = event {
+ self.mode_info = mode_info;
+ }
+ }
+
+ fn render(&mut self, _rows: usize, cols: usize) {
+ let superkey = superkey(self.mode_info.palette);
+ let ctrl_keys = ctrl_keys(&self.mode_info, cols - superkey.len);
+
+ let first_line = format!("{}{}", superkey, ctrl_keys);
+ let second_line = keybinds(&self.mode_info, cols);
+
+ // [48;5;238m is gray background, [0K is so that it fills the rest of the line
+ // [m is background reset, [0K is so that it clears the rest of the line
+ println!(
+ "{}\u{1b}[48;2;{};{};{}m\u{1b}[0K",
+ first_line,
+ self.mode_info.palette.bg.0,
+ self.mode_info.palette.bg.1,
+ self.mode_info.palette.bg.2
+ );
+ println!("\u{1b}[m{}\u{1b}[0K", second_line);
+ }
+}
diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs
new file mode 100644
index 00000000..6ecba662
--- /dev/null
+++ b/default-plugins/status-bar/src/second_line.rs
@@ -0,0 +1,365 @@
+// use colored::*;
+use ansi_term::{ANSIStrings, Color::RGB, Style};
+use zellij_tile::{data::Theme, prelude::*};
+
+use crate::colors::{GREEN, ORANGE, WHITE};
+use crate::{LinePart, MORE_MSG};
+
+fn full_length_shortcut(
+ is_first_shortcut: bool,
+ letter: &str,
+ description: &str,
+ palette: Palette,
+) -> LinePart {
+ let separator = if is_first_shortcut { " " } else { " / " };
+ let separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint(separator);
+ let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
+ let shortcut_left_separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint("<");
+ let shortcut = Style::new()
+ .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .fg(ORANGE)
+ .bold()
+ .paint(letter);
+ let shortcut_right_separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint("> ");
+ let description_len = description.chars().count();
+ let description = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .bold()
+ .paint(description);
+ let len = shortcut_len + description_len + separator.chars().count();
+ LinePart {
+ part: format!(
+ "{}",
+ ANSIStrings(&[
+ separator,
+ shortcut_left_separator,
+ shortcut,
+ shortcut_right_separator,
+ description
+ ])
+ ),
+ len,
+ }
+}
+
+fn first_word_shortcut(
+ is_first_shortcut: bool,
+ letter: &str,
+ description: &str,
+ palette: Palette,
+) -> LinePart {
+ let separator = if is_first_shortcut { " " } else { " / " };
+ let separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint(separator);
+ let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
+ let shortcut_left_separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint("<");
+ let shortcut = Style::new()
+ .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
+ .fg(RGB(palette.green.0, palette.green.1, palette.green.2))
+ .bold()
+ .paint(letter);
+ let shortcut_right_separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint("> ");
+ let description_first_word = description.split(' ').next().unwrap_or("");
+ let description_first_word_length = description_first_word.chars().count();
+ let description_first_word = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .bold()
+ .paint(description_first_word);
+ let len = shortcut_len + description_first_word_length + separator.chars().count();
+ LinePart {
+ part: format!(
+ "{}",
+ ANSIStrings(&[
+ separator,
+ shortcut_left_separator,
+ shortcut,
+ shortcut_right_separator,
+ description_first_word,
+ ])
+ ),
+ len,
+ }
+}
+fn quicknav_full() -> LinePart {
+ let text_first_part = " Tip: ";
+ let alt = "Alt";
+ let text_second_part = " + ";
+ let new_pane_shortcut = "n";
+ let text_third_part = " => open new pane. ";
+ let second_alt = "Alt";
+ let text_fourth_part = " + ";
+ let brackets_navigation = "[]";
+ let text_fifth_part = " or ";
+ let hjkl_navigation = "hjkl";
+ let text_sixths_part = " => navigate between panes.";
+ let len = text_first_part.chars().count()
+ + alt.chars().count()
+ + text_second_part.chars().count()
+ + new_pane_shortcut.chars().count()
+ + text_third_part.chars().count()
+ + second_alt.chars().count()
+ + text_fourth_part.chars().count()
+ + brackets_navigation.chars().count()
+ + text_fifth_part.chars().count()
+ + hjkl_navigation.chars().count()
+ + text_sixths_part.chars().count();
+ LinePart {
+ part: format!(
+ "{}{}{}{}{}{}{}{}{}{}{}",
+ text_first_part,
+ Style::new().fg(ORANGE).bold().paint(alt),
+ text_second_part,
+ Style::new().fg(GREEN).bold().paint(new_pane_shortcut),
+ text_third_part,
+ Style::new().fg(ORANGE).bold().paint(second_alt),
+ text_fourth_part,
+ Style::new().fg(GREEN).bold().paint(brackets_navigation),
+ text_fifth_part,
+ Style::new().fg(GREEN).bold().paint(hjkl_navigation),
+ text_sixths_part,
+ ),
+ len,
+ }
+}
+
+fn quicknav_medium() -> LinePart {
+ let text_first_part = " Tip: ";
+ let alt = "Alt";
+ let text_second_part = " + ";
+ let new_pane_shortcut = "n";
+ let text_third_part = " => new pane. ";
+ let second_alt = "Alt";
+ let text_fourth_part = " + ";
+ let brackets_navigation = "[]";
+ let text_fifth_part = " or ";
+ let hjkl_navigation = "hjkl";
+ let text_sixths_part = " => navigate.";
+ let len = text_first_part.chars().count()
+ + alt.chars().count()
+ + text_second_part.chars().count()
+ + new_pane_shortcut.chars().count()
+ + text_third_part.chars().count()
+ + second_alt.chars().count()
+ + text_fourth_part.chars().count()
+ + brackets_navigation.chars().count()
+ + text_fifth_part.chars().count()
+ + hjkl_navigation.chars().count()
+ + text_sixths_part.chars().count();
+ LinePart {
+ part: format!(
+ "{}{}{}{}{}{}{}{}{}{}{}",
+ text_first_part,
+ Style::new().fg(ORANGE).bold().paint(alt),
+ text_second_part,
+ Style::new().fg(GREEN).bold().paint(new_pane_shortcut),
+ text_third_part,
+ Style::new().fg(ORANGE).bold().paint(second_alt),
+ text_fourth_part,
+ Style::new().fg(GREEN).bold().paint(brackets_navigation),
+ text_fifth_part,
+ Style::new().fg(GREEN).bold().paint(hjkl_navigation),
+ text_sixths_part,
+ ),
+ len,
+ }
+}
+
+fn quicknav_short() -> LinePart {
+ let text_first_part = " QuickNav: ";
+ let alt = "Alt";
+ let text_second_part = " + ";
+ let new_pane_shortcut = "n";
+ let text_third_part = "/";
+ let brackets_navigation = "[]";
+ let text_fifth_part = "/";
+ let hjkl_navigation = "hjkl";
+ let len = text_first_part.chars().count()
+ + alt.chars().count()
+ + text_second_part.chars().count()
+ + new_pane_shortcut.chars().count()
+ + text_third_part.chars().count()
+ + brackets_navigation.chars().count()
+ + text_fifth_part.chars().count()
+ + hjkl_navigation.chars().count();
+ LinePart {
+ part: format!(
+ "{}{}{}{}{}{}{}{}",
+ text_first_part,
+ Style::new().fg(ORANGE).bold().paint(alt),
+ text_second_part,
+ Style::new().fg(GREEN).bold().paint(new_pane_shortcut),
+ text_third_part,
+ Style::new().fg(GREEN).bold().paint(brackets_navigation),
+ text_fifth_part,
+ Style::new().fg(GREEN).bold().paint(hjkl_navigation),
+ ),
+ len,
+ }
+}
+
+fn locked_interface_indication(palette: Palette) -> LinePart {
+ let locked_text = " -- INTERFACE LOCKED -- ";
+ let locked_text_len = locked_text.chars().count();
+ let locked_styled_text = Style::new()
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .bold()
+ .paint(locked_text);
+ LinePart {
+ part: format!("{}", locked_styled_text),
+ len: locked_text_len,
+ }
+}
+
+fn select_pane_shortcut(is_first_shortcut: bool, palette: Palette) -> LinePart {
+ let shortcut = "ENTER";
+ let description = "Select pane";
+ let separator = if is_first_shortcut { " " } else { " / " };
+ let separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint(separator);
+ let shortcut_len = shortcut.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
+ let shortcut_left_separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint("<");
+ let shortcut = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(ORANGE)
+ .bold()
+ .paint(shortcut);
+ let shortcut_right_separator = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .paint("> ");
+ let description_len = description.chars().count();
+ let description = Style::new()
+ .on(RGB(palette.black.0, palette.black.1, palette.black.2))
+ .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
+ .bold()
+ .paint(description);
+ let len = shortcut_len + description_len + separator.chars().count();
+ LinePart {
+ part: format!(
+ "{}",
+ ANSIStrings(&[
+ separator,
+ shortcut_left_separator,
+ shortcut,
+ shortcut_right_separator,
+ description
+ ])
+ ),
+ len,
+ }
+}
+
+fn full_shortcut_list(help: &ModeInfo) -> LinePart {
+ match help.mode {
+ InputMode::Normal => quicknav_full(),
+ InputMode::Locked => locked_interface_indication(help.palette),
+ _ => {
+ let mut line_part = LinePart::default();
+ for (i, (letter, description)) in help.keybinds.iter().enumerate() {
+ let shortcut = full_length_shortcut(i == 0, &letter, &description, help.palette);
+ line_part.len += shortcut.len;
+ line_part.part = format!("{}{}", line_part.part, shortcut,);
+ }
+ let select_pane_shortcut = select_pane_shortcut(help.keybinds.is_empty(), help.palette);
+ line_part.len += select_pane_shortcut.len;
+ line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,);
+ line_part
+ }
+ }
+}
+
+fn shortened_shortcut_list(help: &ModeInfo) -> LinePart {
+ match help.mode {
+ InputMode::Normal => quicknav_medium(),
+ InputMode::Locked => locked_interface_indication(help.palette),
+ _ => {
+ let mut line_part = LinePart::default();
+ for (i, (letter, description)) in help.keybinds.iter().enumerate() {
+ let shortcut = first_word_shortcut(i == 0, &letter, &description, help.palette);
+ line_part.len += shortcut.len;
+ line_part.part = format!("{}{}", line_part.part, shortcut,);
+ }
+ let select_pane_shortcut = select_pane_shortcut(help.keybinds.is_empty(), help.palette);
+ line_part.len += select_pane_shortcut.len;
+ line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,);
+ line_part
+ }
+ }
+}
+
+fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
+ match help.mode {
+ InputMode::Normal => {
+ let line_part = quicknav_short();
+ if line_part.len <= max_len {
+ line_part
+ } else {
+ LinePart::default()
+ }
+ }
+ InputMode::Locked => {
+ let line_part = locked_interface_indication(help.palette);
+ if line_part.len <= max_len {
+ line_part
+ } else {
+ LinePart::default()
+ }
+ }
+ _ => {
+ let mut line_part = LinePart::default();
+ for (i, (letter, description)) in help.keybinds.iter().enumerate() {
+ let shortcut = first_word_shortcut(i == 0, &letter, &description, help.palette);
+ if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len {
+ // TODO: better
+ line_part.part = format!("{}{}", line_part.part, MORE_MSG);
+ line_part.len += MORE_MSG.chars().count();
+ break;
+ }
+ line_part.len += shortcut.len;
+ line_part.part = format!("{}{}", line_part.part, shortcut);
+ }
+ let select_pane_shortcut = select_pane_shortcut(help.keybinds.is_empty(), help.palette);
+ if line_part.len + select_pane_shortcut.len <= max_len {
+ line_part.len += select_pane_shortcut.len;
+ line_part.part = format!("{}{}", line_part.part, select_pane_shortcut,);
+ }
+ line_part
+ }
+ }
+}
+
+pub fn keybinds(help: &ModeInfo, max_width: usize) -> LinePart {
+ let full_shortcut_list = full_shortcut_list(help);
+ if full_shortcut_list.len <= max_width {
+ return full_shortcut_list;
+ }
+ let shortened_shortcut_list = shortened_shortcut_list(help);
+ if shortened_shortcut_list.len <= max_width {
+ return shortened_shortcut_list;
+ }
+ best_effort_shortcut_list(help, max_width)
+}
diff --git a/default-plugins/strider/.cargo/config.toml b/default-plugins/strider/.cargo/config.toml
new file mode 100644
index 00000000..bc255e30
--- /dev/null
+++ b/default-plugins/strider/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi" \ No newline at end of file
diff --git a/default-plugins/strider/Cargo.toml b/default-plugins/strider/Cargo.toml
new file mode 100644
index 00000000..d1059851
--- /dev/null
+++ b/default-plugins/strider/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "strider"
+version = "0.2.0"
+authors = ["Brooks J Rady <b.j.rady@gmail.com>"]
+edition = "2018"
+description = "A simplified ranger clone written as a Zellij plugin"
+license = "MIT"
+
+[dependencies]
+colored = "2"
+zellij-tile = { path = "../../zellij-tile" }
+pretty-bytes = "0.2" \ No newline at end of file
diff --git a/default-plugins/strider/LICENSE.md b/default-plugins/strider/LICENSE.md
new file mode 120000
index 00000000..f0608a63
--- /dev/null
+++ b/default-plugins/strider/LICENSE.md
@@ -0,0 +1 @@
+../../LICENSE.md \ No newline at end of file
diff --git a/default-plugins/strider/src/main.rs b/default-plugins/strider/src/main.rs
new file mode 100644
index 00000000..8edc1e19
--- /dev/null
+++ b/default-plugins/strider/src/main.rs
@@ -0,0 +1,97 @@
+mod state;
+
+use colored::*;
+use state::{FsEntry, State};
+use std::{cmp::min, fs::read_dir};
+use zellij_tile::prelude::*;
+
+register_plugin!(State);
+
+impl ZellijPlugin for State {
+ fn load(&mut self) {
+ refresh_directory(self);
+ subscribe(&[EventType::KeyPress]);
+ }
+
+ fn update(&mut self, event: Event) {
+ if let Event::KeyPress(key) = event {
+ match key {
+ Key::Up | Key::Char('k') => {
+ *self.selected_mut() = self.selected().saturating_sub(1);
+ }
+ Key::Down | Key::Char('j') => {
+ let next = self.selected().saturating_add(1);
+ *self.selected_mut() = min(self.files.len() - 1, next);
+ }
+ Key::Right | Key::Char('\n') | Key::Char('l') if !self.files.is_empty() => {
+ match self.files[self.selected()].clone() {
+ FsEntry::Dir(p, _) => {
+ self.path = p;
+ refresh_directory(self);
+ }
+ FsEntry::File(p, _) => open_file(&p),
+ }
+ }
+ Key::Left | Key::Char('h') => {
+ self.path.pop();
+ refresh_directory(self);
+ }
+
+ Key::Char('.') => {
+ self.toggle_hidden_files();
+ refresh_directory(self);
+ }
+
+ _ => (),
+ };
+ }
+ }
+
+ fn render(&mut self, rows: usize, cols: usize) {
+ for i in 0..rows {
+ if self.selected() < self.scroll() {
+ *self.scroll_mut() = self.selected();
+ }
+ if self.selected() - self.scroll() + 2 > rows {
+ *self.scroll_mut() = self.selected() + 2 - rows;
+ }
+ let i = self.scroll() + i;
+ if let Some(entry) = self.files.get(i) {
+ let mut path = entry.as_line(cols).normal();
+
+ if let FsEntry::Dir(..) = entry {
+ path = path.dimmed().bold();
+ }
+
+ if i == self.selected() {
+ println!("{}", path.reversed());
+ } else {
+ println!("{}", path);
+ }
+ } else {
+ println!();
+ }
+ }
+ }
+}
+
+fn refresh_directory(state: &mut State) {
+ state.files = read_dir(&state.path)
+ .unwrap()
+ .filter_map(|res| {
+ res.and_then(|d| {
+ if d.metadata()?.is_dir() {
+ let children = read_dir(d.path())?.count();
+ Ok(FsEntry::Dir(d.path(), children))
+ } else {
+ let size = d.metadata()?.len();
+ Ok(FsEntry::File(d.path(), size))
+ }
+ })
+ .ok()
+ .filter(|d| !d.is_hidden_file() || !state.hide_hidden_files)
+ })
+ .collect();
+
+ state.files.sort_unstable();
+}
diff --git a/default-plugins/strider/src/state.rs b/default-plugins/strider/src/state.rs
new file mode 100644
index 00000000..0d96ae0b
--- /dev/null
+++ b/default-plugins/strider/src/state.rs
@@ -0,0 +1,63 @@
+use pretty_bytes::converter as pb;
+use std::{collections::HashMap, path::PathBuf};
+
+#[derive(Default)]
+pub struct State {
+ pub path: PathBuf,
+ pub files: Vec<FsEntry>,
+ pub cursor_hist: HashMap<PathBuf, (usize, usize)>,
+ pub hide_hidden_files: bool,
+}
+
+impl State {
+ pub fn selected_mut(&mut self) -> &mut usize {
+ &mut self.cursor_hist.entry(self.path.clone()).or_default().0
+ }
+ pub fn selected(&self) -> usize {
+ self.cursor_hist.get(&self.path).unwrap_or(&(0, 0)).0
+ }
+ pub fn scroll_mut(&mut self) -> &mut usize {
+ &mut self.cursor_hist.entry(self.path.clone()).or_default().1
+ }
+ pub fn scroll(&self) -> usize {
+ self.cursor_hist.get(&self.path).unwrap_or(&(0, 0)).1
+ }
+ pub fn toggle_hidden_files(&mut self) {
+ self.hide_hidden_files = !self.hide_hidden_files;
+ }
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub enum FsEntry {
+ Dir(PathBuf, usize),
+ File(PathBuf, u64),
+}
+
+impl FsEntry {
+ pub fn name(&self) -> String {
+ let path = match self {
+ FsEntry::Dir(p, _) => p,
+ FsEntry::File(p, _) => p,
+ };
+ path.file_name().unwrap().to_string_lossy().into_owned()
+ }
+
+ pub fn as_line(&self, width: usize) -> String {
+ let info = match self {
+ FsEntry::Dir(_, s) => s.to_string(),
+ FsEntry::File(_, s) => pb::convert(*s as f64),
+ };
+ let space = width - info.len();
+ let name = self.name();
+ if space - 1 < name.len() {
+ [&name[..space - 2], &info].join("~ ")
+ } else {
+ let padding = " ".repeat(space - name.len());
+ [name, padding, info].concat()
+ }
+ }
+
+ pub fn is_hidden_file(&self) -> bool {
+ self.name().starts_with('.')
+ }
+}
diff --git a/default-plugins/tab-bar/.cargo/config.toml b/default-plugins/tab-bar/.cargo/config.toml
new file mode 100644
index 00000000..6b77899c
--- /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 00000000..59b95b4b
--- /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 00000000..f0608a63
--- /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 00000000..1d1c33ec
--- /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 00000000..e4a84d98
--- /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 00000000..375d8c46
--- /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)
+ }
+}