use crate::input::actions::Action; use crate::input::config::ConversionError; use clap::ArgEnum; use serde::{Deserialize, Serialize}; use std::fmt; use std::str::FromStr; use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString}; pub type ClientId = u16; // TODO: merge with crate type? pub fn client_id_to_colors( client_id: ClientId, colors: Palette, ) -> Option<(PaletteColor, PaletteColor)> { // (primary color, secondary color) match client_id { 1 => Some((colors.magenta, colors.black)), 2 => Some((colors.blue, colors.black)), 3 => Some((colors.purple, colors.black)), 4 => Some((colors.yellow, colors.black)), 5 => Some((colors.cyan, colors.black)), 6 => Some((colors.gold, colors.black)), 7 => Some((colors.red, colors.black)), 8 => Some((colors.silver, colors.black)), 9 => Some((colors.pink, colors.black)), 10 => Some((colors.brown, colors.black)), _ => None, } } pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) { (colors.green, colors.black) } // TODO: Add a shortened string representation (beyond `Display::fmt` below) that can be used when // screen space is scarce. Useful for e.g. "ENTER", "SPACE", "TAB" to display as Unicode // representations instead. // NOTE: Do not reorder the key variants since that influences what the `status_bar` plugin // displays! #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] pub enum Key { PageDown, PageUp, Left, Down, Up, Right, Home, End, Backspace, Delete, Insert, F(u8), Char(char), Alt(CharOrArrow), Ctrl(char), BackTab, Null, Esc, } impl FromStr for Key { type Err = Box; fn from_str(key_str: &str) -> Result { let mut modifier: Option<&str> = None; let mut main_key: Option<&str> = None; for (index, part) in key_str.split_ascii_whitespace().enumerate() { if index == 0 && (part == "Ctrl" || part == "Alt") { modifier = Some(part); } else if main_key.is_none() { main_key = Some(part) } } match (modifier, main_key) { (Some("Ctrl"), Some(main_key)) => { let mut key_chars = main_key.chars(); let key_count = main_key.chars().count(); if key_count == 1 { let key_char = key_chars.next().unwrap(); Ok(Key::Ctrl(key_char)) } else { Err(format!("Failed to parse key: {}", key_str).into()) } }, (Some("Alt"), Some(main_key)) => { match main_key { // why crate::data::Direction and not just Direction? // Because it's a different type that we export in this wasm mandated soup - we // don't like it either! This will be solved as we chip away at our tech-debt "Left" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Left))), "Right" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Right))), "Up" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Up))), "Down" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Down))), _ => { let mut key_chars = main_key.chars(); let key_count = main_key.chars().count(); if key_count == 1 { let key_char = key_chars.next().unwrap(); Ok(Key::Alt(CharOrArrow::Char(key_char))) } else { Err(format!("Failed to parse key: {}", key_str).into()) } }, } }, (None, Some(main_key)) => match main_key { "Backspace" => Ok(Key::Backspace), "Left" => Ok(Key::Left), "Right" => Ok(Key::Right), "Up" => Ok(Key::Up), "Down" => Ok(Key::Down), "Home" => Ok(Key::Home), "End" => Ok(Key::End), "PageUp" => Ok(Key::PageUp), "PageDown" => Ok(Key::PageDown), "Tab" => Ok(Key::BackTab), "Delete" => Ok(Key::Delete), "Insert" => Ok(Key::Insert), "Space" => Ok(Key::Char(' ')), "Enter" => Ok(Key::Char('\n')), "Esc" => Ok(Key::Esc), _ => { let mut key_chars = main_key.chars(); let key_count = main_key.chars().count(); if key_count == 1 { let key_char = key_chars.next().unwrap(); Ok(Key::Char(key_char)) } else if key_count > 1 { if let Some(first_char) = key_chars.next() { if first_char == 'F' { let f_index: String = key_chars.collect(); let f_index: u8 = f_index .parse() .map_err(|e| format!("Failed to parse F index: {}", e))?; if f_index >= 1 && f_index <= 12 { return Ok(Key::F(f_index)); } } } Err(format!("Failed to parse key: {}", key_str).into()) } else { Err(format!("Failed to parse key: {}", key_str).into()) } }, }, _ => Err(format!("Failed to parse key: {}", key_str).into()), } } } impl fmt::Display for Key { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Key::Backspace => write!(f, "BACKSPACE"), Key::Left => write!(f, "{}", Direction::Left), Key::Right => write!(f, "{}", Direction::Right), Key::Up => write!(f, "{}", Direction::Up), Key::Down => write!(f, "{}", Direction::Down), Key::Home => write!(f, "HOME"), Key::End => write!(f, "END"), Key::PageUp => write!(f, "PgUp"), Key::PageDown => write!(f, "PgDn"), Key::BackTab => write!(f, "TAB"), Key::Delete => write!(f, "DEL"), Key::Insert => write!(f, "INS"), Key::F(n) => write!(f, "F{}", n), Key::Char(c) => match c { '\n' => write!(f, "ENTER"), '\t' => write!(f, "TAB"), ' ' => write!(f, "SPACE"), _ => write!(f, "{}", c), }, Key::Alt(c) => write!(f, "Alt+{}", c), Key::Ctrl(c) => write!(f, "Ctrl+{}", Key::Char(*c)), Key::Null => write!(f, "NULL"), Key::Esc => write!(f, "ESC"), } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] #[serde(untagged)] pub enum CharOrArrow { Char(char), Direction(Direction), } impl fmt::Display for CharOrArrow { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CharOrArrow::Char(c) => write!(f, "{}", Key::Char(*c)), CharOrArrow::Direction(d) => write!(f, "{}", d), } } } /// The four directions (left, right, up, down). #[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] pub enum Direction { Left, Right, Up, Down, } impl Direction { pub fn invert(&self) -> Direction { match *self { Direction::Left => Direction::Right, Direction::Down => Direction::Up, Direction::Up => Direction::Down, Direction::Right => Direction::Left, } } pub fn is_horizontal(&self) -> bool { matches!(self, Direction::Left | Direction::Right) } pub fn is_vertical(&self) -> bool { matches!(self, Direction::Down | Direction::Up) } } impl fmt::Display for Direction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Direction::Left => write!(f, "←"), Direction::Right => write!(f, "→"), Direction::Up => write!(f, "↑"), Direction::Down => write!(f, "↓"), } } } impl FromStr for Direction { type Err = String; fn from_str(s: &str) -> Result { match s { "Left" | "left" => Ok(Direction::Left), "Right" | "right" => Ok(Direction::Right), "Up" | "up" => Ok(Direction::Up), "Down" | "down" => Ok(Direction::Down), _ => Err(format!( "Failed to parse Direction. Unknown Direction: {}", s )), } } } /// Resize operation to perform. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)] pub enum Resize { Increase, Decrease, } impl Resize { pub fn invert(&self) -> Self { match self { Resize::Increase => Resize::Decrease, Resize::Decrease => Resize::Increase, } } } impl fmt::Display for Resize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Resize::Increase => write!(f, "+"), Resize::Decrease => write!(f, "-"), } } } impl FromStr for Resize { type Err = String; fn from_str(s: &str) -> Result { match s { "Increase" | "increase" | "+" => Ok(Resize::Increase), "Decrease" | "decrease" | "-" => Ok(Resize::Decrease), _ => Err(format!( "failed to parse resize type. Unknown specifier '{}'", s )), } } } /// Container type that fully describes resize operations. /// /// This is best thought of as follows: /// /// - `resize` commands how the total *area* of the pane will change as part of this resize /// operation. /// - `direction` has two meanings: /// - `None` means to resize all borders equally /// - Anything else means to move the named border to achieve the change in area #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)] pub struct ResizeStrategy { /// Whether to increase or resize total area pub resize: Resize, /// With which border, if any, to change area pub direction: Option, /// If set to true (default), increasing resizes towards a viewport border will be inverted. /// I.e. a scenario like this ("increase right"): /// /// ```text /// +---+---+ /// | | X |-> /// +---+---+ /// ``` /// /// turns into this ("decrease left"): /// /// ```text /// +---+---+ /// | |-> | /// +---+---+ /// ``` pub invert_on_boundaries: bool, } impl From for ResizeStrategy { fn from(direction: Direction) -> Self { ResizeStrategy::new(Resize::Increase, Some(direction)) } } impl From for ResizeStrategy { fn from(resize: Resize) -> Self { ResizeStrategy::new(resize, None) } } impl ResizeStrategy { pub fn new(resize: Resize, direction: Option) -> Self { ResizeStrategy { resize, direction, invert_on_boundaries: true, } } pub fn invert(&self) -> ResizeStrategy { let resize = match self.resize { Resize::Increase => Resize::Decrease, Resize::Decrease => Resize::Increase, }; let direction = match self.direction { Some(direction) => Some(direction.invert()), None => None, }; ResizeStrategy::new(resize, direction) } pub fn resize_type(&self) -> Resize { self.resize } pub fn direction(&self) -> Option { self.direction } pub fn direction_horizontal(&self) -> bool { matches!( self.direction, Some(Direction::Left) | Some(Direction::Right) ) } pub fn direction_vertical(&self) -> bool { matches!(self.direction, Some(Direction::Up) | Some(Direction::Down)) } pub fn resize_increase(&self) -> bool { self.resize == Resize::Increase } pub fn resize_decrease(&self) -> bool { self.resize == Resize::Decrease } pub fn move_left_border_left(&self) -> bool { (self.resize == Resize::Increase) && (self.direction == Some(Direction::Left)) } pub fn move_left_border_right(&self) -> bool { (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Left)) } pub fn move_lower_border_down(&self) -> bool { (self.resize == Resize::Increase) && (self.direction == Some(Direction::Down)) } pub fn move_lower_border_up(&self) -> bool { (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Down)) } pub fn move_upper_border_up(&self) -> bool { (self.resize == Resize::Increase) && (self.direction == Some(Direction::Up)) } pub fn move_upper_border_down(&self) -> bool { (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Up)) } pub fn move_right_border_right(&self) -> bool { (self.resize == Resize::Increase) && (self.direction == Some(Direction::Right)) } pub fn move_right_border_left(&self) -> bool { (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Right)) } pub fn move_all_borders_out(&self) -> bool { (self.resize == Resize::Increase) && (self.direction == None) } pub fn move_all_borders_in(&self) -> bool { (self.resize == Resize::Decrease) && (self.direction == None) } } impl fmt::Display for ResizeStrategy { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let resize = match self.resize { Resize::Increase => "increase", Resize::Decrease => "decrease", }; let border = match self.direction { Some(Direction::Left) => "left", Some(Direction::Down) => "bottom", Some(Direction::Up) => "top", Some(Direction::Right) => "right", None => "every", }; write!(f, "{} size on {} border", resize, border) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] // FIXME: This should be extended to handle different button clicks (not just // left click) and the `ScrollUp` and `ScrollDown` events could probably be // merged into a single `Scroll(isize)` event. pub enum Mouse { ScrollUp(usize), // number of lines ScrollDown(usize), // number of lines LeftClick(isize, usize), // line and column RightClick(isize, usize), // line and column Hold(isize, usize), // line and column Release(isize, usize), // line and column } #[derive(Debug, Clone, PartialEq, EnumDiscriminants, ToString, Serialize, Deserialize)] #[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))] #[strum_discriminants(name(EventType))] #[non_exhaustive] pub enum Event { ModeUpdate(ModeInfo), TabUpdate(Vec), Key(Key), Mouse(Mouse), Timer(f64), CopyToClipboard(CopyDestination), SystemClipboardFailure, InputReceived, Visible(bool), } /// Describes the different input modes, which change the way that keystrokes will be interpreted. #[derive( Debug, PartialEq, Eq, Hash, Copy, Clone, EnumIter, Serialize, Deserialize, ArgEnum, PartialOrd, Ord, )] pub enum InputMode { /// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading /// to other modes #[serde(alias = "normal")] Normal, /// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled /// except the one leading back to normal mode #[serde(alias = "locked")] Locked, /// `Resize` mode allows resizing the different existing panes. #[serde(alias = "resize")] Resize, /// `Pane` mode allows creating and closing panes, as well as moving between them. #[serde(alias = "pane")] Pane, /// `Tab` mode allows creating and closing tabs, as well as moving between them. #[serde(alias = "tab")] Tab, /// `Scroll` mode allows scrolling up and down within a pane. #[serde(alias = "scroll")] Scroll, /// `EnterSearch` mode allows for typing in the needle for a search in the scroll buffer of a pane. #[serde(alias = "entersearch")] EnterSearch, /// `Search` mode allows for searching a term in a pane (superset of `Scroll`). #[serde(alias = "search")] Search, /// `RenameTab` mode allows assigning a new name to a tab. #[serde(alias = "renametab")] RenameTab, /// `RenamePane` mode allows assigning a new name to a pane. #[serde(alias = "renamepane")] RenamePane, /// `Session` mode allows detaching sessions #[serde(alias = "session")] Session, /// `Move` mode allows moving the different existing panes within a tab #[serde(alias = "move")] Move, /// `Prompt` mode allows interacting with active prompts. #[serde(alias = "prompt")] Prompt, /// `Tmux` mode allows for basic tmux keybindings functionality #[serde(alias = "tmux")] Tmux, } // impl TryFrom<&str> for InputMode { // type Error = String; // fn try_from(mode: &str) -> Result { // match mode { // "normal" | "Normal" => Ok(InputMode::Normal), // "locked" | "Locked" => Ok(InputMode::Locked), // "resize" | "Resize" => Ok(InputMode::Resize), // "pane" | "Pane" => Ok(InputMode::Pane), // "tab" | "Tab" => Ok(InputMode::Tab), // "search" | "Search" => Ok(InputMode::Search), // "renametab" | "RenameTab" => Ok(InputMode::RenameTab), // "renamepane" | "RenamePane" => Ok(InputMode::RenamePane), // "session" | "Session" => Ok(InputMode::Session), // "move" | "Move" => Ok(InputMode::Move), // "prompt" | "Prompt" => Ok(InputMode::Prompt), // "tmux" | "Tmux" => Ok(InputMode::Tmux), // _ => Err(format!("Unrecognized mode: {}", mode)), // } // } // } impl Default for InputMode { fn default() -> InputMode { InputMode::Normal } } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum ThemeHue { Light, Dark, } impl Default for ThemeHue { fn default() -> ThemeHue { ThemeHue::Dark } } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PaletteColor { Rgb((u8, u8, u8)), EightBit(u8), } impl Default for PaletteColor { fn default() -> PaletteColor { PaletteColor::EightBit(0) } } impl FromStr for InputMode { type Err = ConversionError; fn from_str(s: &str) -> Result { match s { "normal" | "Normal" => Ok(InputMode::Normal), "locked" | "Locked" => Ok(InputMode::Locked), "resize" | "Resize" => Ok(InputMode::Resize), "pane" | "Pane" => Ok(InputMode::Pane), "tab" | "Tab" => Ok(InputMode::Tab), "search" | "Search" => Ok(InputMode::Search), "scroll" | "Scroll" => Ok(InputMode::Scroll), "renametab" | "RenameTab" => Ok(InputMode::RenameTab), "renamepane" | "RenamePane" => Ok(InputMode::RenamePane), "session" | "Session" => Ok(InputMode::Session), "move" | "Move" => Ok(InputMode::Move), "prompt" | "Prompt" => Ok(InputMode::Prompt), "tmux" | "Tmux" => Ok(InputMode::Tmux), "entersearch" | "Entersearch" | "EnterSearch" => Ok(InputMode::EnterSearch), e => Err(ConversionError::UnknownInputMode(e.into())), } } } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PaletteSource { Default, Xresources, } impl Default for PaletteSource { fn default() -> PaletteSource { PaletteSource::Default } } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Default)] pub struct Palette { pub source: PaletteSource, pub theme_hue: ThemeHue, pub fg: PaletteColor, pub bg: PaletteColor, pub black: PaletteColor, pub red: PaletteColor, pub green: PaletteColor, pub yellow: PaletteColor, pub blue: PaletteColor, pub magenta: PaletteColor, pub cyan: PaletteColor, pub white: PaletteColor, pub orange: PaletteColor, pub gray: PaletteColor, pub purple: PaletteColor, pub gold: PaletteColor, pub silver: PaletteColor, pub pink: PaletteColor, pub brown: PaletteColor, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct Style { pub colors: Palette, pub rounded_corners: bool, } // FIXME: Poor devs hashtable since HashTable can't derive `Default`... pub type KeybindsVec = Vec<(InputMode, Vec<(Key, Vec)>)>; /// Represents the contents of the help message that is printed in the status bar, /// which indicates the current [`InputMode`] and what the keybinds for that mode /// are. Related to the default `status-bar` plugin. #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ModeInfo { pub mode: InputMode, pub keybinds: KeybindsVec, pub style: Style, pub capabilities: PluginCapabilities, pub session_name: Option, } impl ModeInfo { pub fn get_mode_keybinds(&self) -> Vec<(Key, Vec)> { self.get_keybinds_for_mode(self.mode) } pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(Key, Vec)> { for (vec_mode, map) in &self.keybinds { if mode == *vec_mode { return map.to_vec(); } } vec![] } } #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct TabInfo { /* subset of fields to publish to plugins */ pub position: usize, pub name: String, pub active: bool, pub panes_to_hide: usize, pub is_fullscreen_active: bool, pub is_sync_panes_active: bool, pub are_floating_panes_visible: bool, pub other_focused_clients: Vec, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct PluginIds { pub plugin_id: u32, pub zellij_pid: u32, } /// Tag used to identify the plugin in layout and config yaml files #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd, Ord)] pub struct PluginTag(String); impl PluginTag { pub fn new(url: impl Into) -> Self { PluginTag(url.into()) } } impl From for String { fn from(tag: PluginTag) -> Self { tag.0 } } impl fmt::Display for PluginTag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct PluginCapabilities { pub arrow_fonts: bool, } impl Default for PluginCapabilities { fn default() -> PluginCapabilities { PluginCapabilities { arrow_fonts: true } } } #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum CopyDestination { Command, Primary, System, }