diff options
| author | denis <denismaximov98@gmail.com> | 2021-04-22 11:45:29 +0300 |
|---|---|---|
| committer | denis <denismaximov98@gmail.com> | 2021-04-22 11:45:29 +0300 |
| commit | 8d98ca7da0bc278a832e4e7de23ff63a84deb529 (patch) | |
| tree | e7fdfaa6c1ae25e7737dbb36bd1dbcb94d62ab47 /default-plugins | |
| parent | wip: fixing the first arrow (diff) | |
| parent | fix(cargo): reduce crate size by 4.8MB using `cargo diet`, to 77kB (diff) | |
| download | sl0ppij-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.toml | 2 | ||||
| -rw-r--r-- | default-plugins/status-bar/Cargo.toml | 11 | ||||
| l--------- | default-plugins/status-bar/LICENSE.md | 1 | ||||
| -rw-r--r-- | default-plugins/status-bar/src/first_line.rs | 395 | ||||
| -rw-r--r-- | default-plugins/status-bar/src/main.rs | 76 | ||||
| -rw-r--r-- | default-plugins/status-bar/src/second_line.rs | 365 | ||||
| -rw-r--r-- | default-plugins/strider/.cargo/config.toml | 2 | ||||
| -rw-r--r-- | default-plugins/strider/Cargo.toml | 12 | ||||
| l--------- | default-plugins/strider/LICENSE.md | 1 | ||||
| -rw-r--r-- | default-plugins/strider/src/main.rs | 97 | ||||
| -rw-r--r-- | default-plugins/strider/src/state.rs | 63 | ||||
| -rw-r--r-- | default-plugins/tab-bar/.cargo/config.toml | 2 | ||||
| -rw-r--r-- | default-plugins/tab-bar/Cargo.toml | 11 | ||||
| l--------- | default-plugins/tab-bar/LICENSE.md | 1 | ||||
| -rw-r--r-- | default-plugins/tab-bar/src/line.rs | 180 | ||||
| -rw-r--r-- | default-plugins/tab-bar/src/main.rs | 84 | ||||
| -rw-r--r-- | default-plugins/tab-bar/src/tab.rs | 67 |
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) + } +} |
