zellij_utils/
data.rs

1use crate::home::default_layout_dir;
2use crate::input::actions::Action;
3use crate::input::config::ConversionError;
4use crate::input::keybinds::Keybinds;
5use crate::input::layout::{RunPlugin, SplitSize};
6use crate::pane_size::PaneGeom;
7use crate::shared::{colors as default_colors, eightbit_to_rgb};
8use clap::ArgEnum;
9use serde::{Deserialize, Serialize};
10use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
11use std::fmt;
12use std::fs::Metadata;
13use std::net::IpAddr;
14use std::path::{Path, PathBuf};
15use std::str::{self, FromStr};
16use std::time::Duration;
17use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString, ToString};
18
19#[cfg(not(target_family = "wasm"))]
20use termwiz::{
21    escape::csi::KittyKeyboardFlags,
22    input::{KeyCode, KeyCodeEncodeModes, KeyboardEncoding, Modifiers},
23};
24
25pub type ClientId = u16; // TODO: merge with crate type?
26
27pub fn client_id_to_colors(
28    client_id: ClientId,
29    colors: MultiplayerColors,
30) -> Option<(PaletteColor, PaletteColor)> {
31    // (primary color, secondary color)
32    let black = PaletteColor::EightBit(default_colors::BLACK);
33    match client_id {
34        1 => Some((colors.player_1, black)),
35        2 => Some((colors.player_2, black)),
36        3 => Some((colors.player_3, black)),
37        4 => Some((colors.player_4, black)),
38        5 => Some((colors.player_5, black)),
39        6 => Some((colors.player_6, black)),
40        7 => Some((colors.player_7, black)),
41        8 => Some((colors.player_8, black)),
42        9 => Some((colors.player_9, black)),
43        10 => Some((colors.player_10, black)),
44        _ => None,
45    }
46}
47
48pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) {
49    (colors.green, colors.black)
50}
51
52impl FromStr for KeyWithModifier {
53    type Err = Box<dyn std::error::Error>;
54    fn from_str(key_str: &str) -> Result<Self, Self::Err> {
55        let mut key_string_parts: Vec<&str> = key_str.split_ascii_whitespace().collect();
56        let bare_key: BareKey = BareKey::from_str(key_string_parts.pop().ok_or("empty key")?)?;
57        let mut key_modifiers: BTreeSet<KeyModifier> = BTreeSet::new();
58        for stringified_modifier in key_string_parts {
59            key_modifiers.insert(KeyModifier::from_str(stringified_modifier)?);
60        }
61        Ok(KeyWithModifier {
62            bare_key,
63            key_modifiers,
64        })
65    }
66}
67
68#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialOrd, Ord)]
69pub struct KeyWithModifier {
70    pub bare_key: BareKey,
71    pub key_modifiers: BTreeSet<KeyModifier>,
72}
73
74impl PartialEq for KeyWithModifier {
75    fn eq(&self, other: &Self) -> bool {
76        match (self.bare_key, other.bare_key) {
77            (BareKey::Char(self_char), BareKey::Char(other_char))
78                if self_char.to_ascii_lowercase() == other_char.to_ascii_lowercase() =>
79            {
80                let mut self_cloned = self.clone();
81                let mut other_cloned = other.clone();
82                if self_char.is_ascii_uppercase() {
83                    self_cloned.bare_key = BareKey::Char(self_char.to_ascii_lowercase());
84                    self_cloned.key_modifiers.insert(KeyModifier::Shift);
85                }
86                if other_char.is_ascii_uppercase() {
87                    other_cloned.bare_key = BareKey::Char(self_char.to_ascii_lowercase());
88                    other_cloned.key_modifiers.insert(KeyModifier::Shift);
89                }
90                self_cloned.bare_key == other_cloned.bare_key
91                    && self_cloned.key_modifiers == other_cloned.key_modifiers
92            },
93            _ => self.bare_key == other.bare_key && self.key_modifiers == other.key_modifiers,
94        }
95    }
96}
97
98impl Hash for KeyWithModifier {
99    fn hash<H: Hasher>(&self, state: &mut H) {
100        match self.bare_key {
101            BareKey::Char(character) if character.is_ascii_uppercase() => {
102                let mut to_hash = self.clone();
103                to_hash.bare_key = BareKey::Char(character.to_ascii_lowercase());
104                to_hash.key_modifiers.insert(KeyModifier::Shift);
105                to_hash.bare_key.hash(state);
106                to_hash.key_modifiers.hash(state);
107            },
108            _ => {
109                self.bare_key.hash(state);
110                self.key_modifiers.hash(state);
111            },
112        }
113    }
114}
115
116impl fmt::Display for KeyWithModifier {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        if self.key_modifiers.is_empty() {
119            write!(f, "{}", self.bare_key)
120        } else {
121            write!(
122                f,
123                "{} {}",
124                self.key_modifiers
125                    .iter()
126                    .map(|m| m.to_string())
127                    .collect::<Vec<_>>()
128                    .join(" "),
129                self.bare_key
130            )
131        }
132    }
133}
134
135#[cfg(not(target_family = "wasm"))]
136impl Into<Modifiers> for &KeyModifier {
137    fn into(self) -> Modifiers {
138        match self {
139            KeyModifier::Shift => Modifiers::SHIFT,
140            KeyModifier::Alt => Modifiers::ALT,
141            KeyModifier::Ctrl => Modifiers::CTRL,
142            KeyModifier::Super => Modifiers::SUPER,
143        }
144    }
145}
146
147#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
148pub enum BareKey {
149    PageDown,
150    PageUp,
151    Left,
152    Down,
153    Up,
154    Right,
155    Home,
156    End,
157    Backspace,
158    Delete,
159    Insert,
160    F(u8),
161    Char(char),
162    Tab,
163    Esc,
164    Enter,
165    CapsLock,
166    ScrollLock,
167    NumLock,
168    PrintScreen,
169    Pause,
170    Menu,
171}
172
173impl fmt::Display for BareKey {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        match self {
176            BareKey::PageDown => write!(f, "PgDn"),
177            BareKey::PageUp => write!(f, "PgUp"),
178            BareKey::Left => write!(f, "←"),
179            BareKey::Down => write!(f, "↓"),
180            BareKey::Up => write!(f, "↑"),
181            BareKey::Right => write!(f, "→"),
182            BareKey::Home => write!(f, "HOME"),
183            BareKey::End => write!(f, "END"),
184            BareKey::Backspace => write!(f, "BACKSPACE"),
185            BareKey::Delete => write!(f, "DEL"),
186            BareKey::Insert => write!(f, "INS"),
187            BareKey::F(index) => write!(f, "F{}", index),
188            BareKey::Char(' ') => write!(f, "SPACE"),
189            BareKey::Char(character) => write!(f, "{}", character),
190            BareKey::Tab => write!(f, "TAB"),
191            BareKey::Esc => write!(f, "ESC"),
192            BareKey::Enter => write!(f, "ENTER"),
193            BareKey::CapsLock => write!(f, "CAPSlOCK"),
194            BareKey::ScrollLock => write!(f, "SCROLLlOCK"),
195            BareKey::NumLock => write!(f, "NUMLOCK"),
196            BareKey::PrintScreen => write!(f, "PRINTSCREEN"),
197            BareKey::Pause => write!(f, "PAUSE"),
198            BareKey::Menu => write!(f, "MENU"),
199        }
200    }
201}
202
203impl FromStr for BareKey {
204    type Err = Box<dyn std::error::Error>;
205    fn from_str(key_str: &str) -> Result<Self, Self::Err> {
206        match key_str.to_ascii_lowercase().as_str() {
207            "pagedown" => Ok(BareKey::PageDown),
208            "pageup" => Ok(BareKey::PageUp),
209            "left" => Ok(BareKey::Left),
210            "down" => Ok(BareKey::Down),
211            "up" => Ok(BareKey::Up),
212            "right" => Ok(BareKey::Right),
213            "home" => Ok(BareKey::Home),
214            "end" => Ok(BareKey::End),
215            "backspace" => Ok(BareKey::Backspace),
216            "delete" => Ok(BareKey::Delete),
217            "insert" => Ok(BareKey::Insert),
218            "f1" => Ok(BareKey::F(1)),
219            "f2" => Ok(BareKey::F(2)),
220            "f3" => Ok(BareKey::F(3)),
221            "f4" => Ok(BareKey::F(4)),
222            "f5" => Ok(BareKey::F(5)),
223            "f6" => Ok(BareKey::F(6)),
224            "f7" => Ok(BareKey::F(7)),
225            "f8" => Ok(BareKey::F(8)),
226            "f9" => Ok(BareKey::F(9)),
227            "f10" => Ok(BareKey::F(10)),
228            "f11" => Ok(BareKey::F(11)),
229            "f12" => Ok(BareKey::F(12)),
230            "tab" => Ok(BareKey::Tab),
231            "esc" => Ok(BareKey::Esc),
232            "enter" => Ok(BareKey::Enter),
233            "capslock" => Ok(BareKey::CapsLock),
234            "scrolllock" => Ok(BareKey::ScrollLock),
235            "numlock" => Ok(BareKey::NumLock),
236            "printscreen" => Ok(BareKey::PrintScreen),
237            "pause" => Ok(BareKey::Pause),
238            "menu" => Ok(BareKey::Menu),
239            "space" => Ok(BareKey::Char(' ')),
240            _ => {
241                if key_str.chars().count() == 1 {
242                    if let Some(character) = key_str.chars().next() {
243                        return Ok(BareKey::Char(character));
244                    }
245                }
246                Err("unsupported key".into())
247            },
248        }
249    }
250}
251
252#[derive(
253    Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, ToString,
254)]
255pub enum KeyModifier {
256    Ctrl,
257    Alt,
258    Shift,
259    Super,
260}
261
262impl FromStr for KeyModifier {
263    type Err = Box<dyn std::error::Error>;
264    fn from_str(key_str: &str) -> Result<Self, Self::Err> {
265        match key_str.to_ascii_lowercase().as_str() {
266            "shift" => Ok(KeyModifier::Shift),
267            "alt" => Ok(KeyModifier::Alt),
268            "ctrl" => Ok(KeyModifier::Ctrl),
269            "super" => Ok(KeyModifier::Super),
270            _ => Err("unsupported modifier".into()),
271        }
272    }
273}
274
275impl BareKey {
276    pub fn from_bytes_with_u(bytes: &[u8]) -> Option<Self> {
277        match str::from_utf8(bytes) {
278            Ok("27") => Some(BareKey::Esc),
279            Ok("13") => Some(BareKey::Enter),
280            Ok("9") => Some(BareKey::Tab),
281            Ok("127") => Some(BareKey::Backspace),
282            Ok("57358") => Some(BareKey::CapsLock),
283            Ok("57359") => Some(BareKey::ScrollLock),
284            Ok("57360") => Some(BareKey::NumLock),
285            Ok("57361") => Some(BareKey::PrintScreen),
286            Ok("57362") => Some(BareKey::Pause),
287            Ok("57363") => Some(BareKey::Menu),
288            Ok("57399") => Some(BareKey::Char('0')),
289            Ok("57400") => Some(BareKey::Char('1')),
290            Ok("57401") => Some(BareKey::Char('2')),
291            Ok("57402") => Some(BareKey::Char('3')),
292            Ok("57403") => Some(BareKey::Char('4')),
293            Ok("57404") => Some(BareKey::Char('5')),
294            Ok("57405") => Some(BareKey::Char('6')),
295            Ok("57406") => Some(BareKey::Char('7')),
296            Ok("57407") => Some(BareKey::Char('8')),
297            Ok("57408") => Some(BareKey::Char('9')),
298            Ok("57409") => Some(BareKey::Char('.')),
299            Ok("57410") => Some(BareKey::Char('/')),
300            Ok("57411") => Some(BareKey::Char('*')),
301            Ok("57412") => Some(BareKey::Char('-')),
302            Ok("57413") => Some(BareKey::Char('+')),
303            Ok("57414") => Some(BareKey::Enter),
304            Ok("57415") => Some(BareKey::Char('=')),
305            Ok("57417") => Some(BareKey::Left),
306            Ok("57418") => Some(BareKey::Right),
307            Ok("57419") => Some(BareKey::Up),
308            Ok("57420") => Some(BareKey::Down),
309            Ok("57421") => Some(BareKey::PageUp),
310            Ok("57422") => Some(BareKey::PageDown),
311            Ok("57423") => Some(BareKey::Home),
312            Ok("57424") => Some(BareKey::End),
313            Ok("57425") => Some(BareKey::Insert),
314            Ok("57426") => Some(BareKey::Delete),
315            Ok(num) => u8::from_str_radix(num, 10)
316                .ok()
317                .map(|n| BareKey::Char((n as char).to_ascii_lowercase())),
318            _ => None,
319        }
320    }
321    pub fn from_bytes_with_tilde(bytes: &[u8]) -> Option<Self> {
322        match str::from_utf8(bytes) {
323            Ok("2") => Some(BareKey::Insert),
324            Ok("3") => Some(BareKey::Delete),
325            Ok("5") => Some(BareKey::PageUp),
326            Ok("6") => Some(BareKey::PageDown),
327            Ok("7") => Some(BareKey::Home),
328            Ok("8") => Some(BareKey::End),
329            Ok("11") => Some(BareKey::F(1)),
330            Ok("12") => Some(BareKey::F(2)),
331            Ok("13") => Some(BareKey::F(3)),
332            Ok("14") => Some(BareKey::F(4)),
333            Ok("15") => Some(BareKey::F(5)),
334            Ok("17") => Some(BareKey::F(6)),
335            Ok("18") => Some(BareKey::F(7)),
336            Ok("19") => Some(BareKey::F(8)),
337            Ok("20") => Some(BareKey::F(9)),
338            Ok("21") => Some(BareKey::F(10)),
339            Ok("23") => Some(BareKey::F(11)),
340            Ok("24") => Some(BareKey::F(12)),
341            _ => None,
342        }
343    }
344    pub fn from_bytes_with_no_ending_byte(bytes: &[u8]) -> Option<Self> {
345        match str::from_utf8(bytes) {
346            Ok("1D") | Ok("D") => Some(BareKey::Left),
347            Ok("1C") | Ok("C") => Some(BareKey::Right),
348            Ok("1A") | Ok("A") => Some(BareKey::Up),
349            Ok("1B") | Ok("B") => Some(BareKey::Down),
350            Ok("1H") | Ok("H") => Some(BareKey::Home),
351            Ok("1F") | Ok("F") => Some(BareKey::End),
352            Ok("1P") | Ok("P") => Some(BareKey::F(1)),
353            Ok("1Q") | Ok("Q") => Some(BareKey::F(2)),
354            Ok("1S") | Ok("S") => Some(BareKey::F(4)),
355            _ => None,
356        }
357    }
358}
359
360bitflags::bitflags! {
361    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
362    struct ModifierFlags: u8 {
363        const SHIFT   = 0b0000_0001;
364        const ALT     = 0b0000_0010;
365        const CONTROL = 0b0000_0100;
366        const SUPER   = 0b0000_1000;
367        // we don't actually use the below, left here for completeness in case we want to add them
368        // later
369        const HYPER = 0b0001_0000;
370        const META = 0b0010_0000;
371        const CAPS_LOCK = 0b0100_0000;
372        const NUM_LOCK = 0b1000_0000;
373    }
374}
375
376impl KeyModifier {
377    pub fn from_bytes(bytes: &[u8]) -> BTreeSet<KeyModifier> {
378        let modifier_flags = str::from_utf8(bytes)
379            .ok() // convert to string: (eg. "16")
380            .and_then(|s| u8::from_str_radix(&s, 10).ok()) // convert to u8: (eg. 16)
381            .map(|s| s.saturating_sub(1)) // subtract 1: (eg. 15)
382            .and_then(|b| ModifierFlags::from_bits(b)); // bitflags: (0b0000_1111: Shift, Alt, Control, Super)
383        let mut key_modifiers = BTreeSet::new();
384        if let Some(modifier_flags) = modifier_flags {
385            for name in modifier_flags.iter() {
386                match name {
387                    ModifierFlags::SHIFT => key_modifiers.insert(KeyModifier::Shift),
388                    ModifierFlags::ALT => key_modifiers.insert(KeyModifier::Alt),
389                    ModifierFlags::CONTROL => key_modifiers.insert(KeyModifier::Ctrl),
390                    ModifierFlags::SUPER => key_modifiers.insert(KeyModifier::Super),
391                    _ => false,
392                };
393            }
394        }
395        key_modifiers
396    }
397}
398
399impl KeyWithModifier {
400    pub fn new(bare_key: BareKey) -> Self {
401        KeyWithModifier {
402            bare_key,
403            key_modifiers: BTreeSet::new(),
404        }
405    }
406    pub fn new_with_modifiers(bare_key: BareKey, key_modifiers: BTreeSet<KeyModifier>) -> Self {
407        KeyWithModifier {
408            bare_key,
409            key_modifiers,
410        }
411    }
412    pub fn with_shift_modifier(mut self) -> Self {
413        self.key_modifiers.insert(KeyModifier::Shift);
414        self
415    }
416    pub fn with_alt_modifier(mut self) -> Self {
417        self.key_modifiers.insert(KeyModifier::Alt);
418        self
419    }
420    pub fn with_ctrl_modifier(mut self) -> Self {
421        self.key_modifiers.insert(KeyModifier::Ctrl);
422        self
423    }
424    pub fn with_super_modifier(mut self) -> Self {
425        self.key_modifiers.insert(KeyModifier::Super);
426        self
427    }
428    pub fn from_bytes_with_u(number_bytes: &[u8], modifier_bytes: &[u8]) -> Option<Self> {
429        // CSI number ; modifiers u
430        let bare_key = BareKey::from_bytes_with_u(number_bytes);
431        match bare_key {
432            Some(bare_key) => {
433                let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
434                Some(KeyWithModifier {
435                    bare_key,
436                    key_modifiers,
437                })
438            },
439            _ => None,
440        }
441    }
442    pub fn from_bytes_with_tilde(number_bytes: &[u8], modifier_bytes: &[u8]) -> Option<Self> {
443        // CSI number ; modifiers ~
444        let bare_key = BareKey::from_bytes_with_tilde(number_bytes);
445        match bare_key {
446            Some(bare_key) => {
447                let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
448                Some(KeyWithModifier {
449                    bare_key,
450                    key_modifiers,
451                })
452            },
453            _ => None,
454        }
455    }
456    pub fn from_bytes_with_no_ending_byte(
457        number_bytes: &[u8],
458        modifier_bytes: &[u8],
459    ) -> Option<Self> {
460        // CSI 1; modifiers [ABCDEFHPQS]
461        let bare_key = BareKey::from_bytes_with_no_ending_byte(number_bytes);
462        match bare_key {
463            Some(bare_key) => {
464                let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
465                Some(KeyWithModifier {
466                    bare_key,
467                    key_modifiers,
468                })
469            },
470            _ => None,
471        }
472    }
473    pub fn strip_common_modifiers(&self, common_modifiers: &Vec<KeyModifier>) -> Self {
474        let common_modifiers: BTreeSet<&KeyModifier> = common_modifiers.into_iter().collect();
475        KeyWithModifier {
476            bare_key: self.bare_key.clone(),
477            key_modifiers: self
478                .key_modifiers
479                .iter()
480                .filter(|m| !common_modifiers.contains(m))
481                .cloned()
482                .collect(),
483        }
484    }
485    pub fn is_key_without_modifier(&self, key: BareKey) -> bool {
486        self.bare_key == key && self.key_modifiers.is_empty()
487    }
488    pub fn is_key_with_ctrl_modifier(&self, key: BareKey) -> bool {
489        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Ctrl)
490    }
491    pub fn is_key_with_alt_modifier(&self, key: BareKey) -> bool {
492        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Alt)
493    }
494    pub fn is_key_with_shift_modifier(&self, key: BareKey) -> bool {
495        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Shift)
496    }
497    pub fn is_key_with_super_modifier(&self, key: BareKey) -> bool {
498        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Super)
499    }
500    pub fn is_cancel_key(&self) -> bool {
501        // self.bare_key == BareKey::Esc || self.is_key_with_ctrl_modifier(BareKey::Char('c'))
502        self.bare_key == BareKey::Esc
503    }
504    #[cfg(not(target_family = "wasm"))]
505    pub fn to_termwiz_modifiers(&self) -> Modifiers {
506        let mut modifiers = Modifiers::empty();
507        for modifier in &self.key_modifiers {
508            modifiers.set(modifier.into(), true);
509        }
510        modifiers
511    }
512    #[cfg(not(target_family = "wasm"))]
513    pub fn to_termwiz_keycode(&self) -> KeyCode {
514        match self.bare_key {
515            BareKey::PageDown => KeyCode::PageDown,
516            BareKey::PageUp => KeyCode::PageUp,
517            BareKey::Left => KeyCode::LeftArrow,
518            BareKey::Down => KeyCode::DownArrow,
519            BareKey::Up => KeyCode::UpArrow,
520            BareKey::Right => KeyCode::RightArrow,
521            BareKey::Home => KeyCode::Home,
522            BareKey::End => KeyCode::End,
523            BareKey::Backspace => KeyCode::Backspace,
524            BareKey::Delete => KeyCode::Delete,
525            BareKey::Insert => KeyCode::Insert,
526            BareKey::F(index) => KeyCode::Function(index),
527            BareKey::Char(character) => KeyCode::Char(character),
528            BareKey::Tab => KeyCode::Tab,
529            BareKey::Esc => KeyCode::Escape,
530            BareKey::Enter => KeyCode::Enter,
531            BareKey::CapsLock => KeyCode::CapsLock,
532            BareKey::ScrollLock => KeyCode::ScrollLock,
533            BareKey::NumLock => KeyCode::NumLock,
534            BareKey::PrintScreen => KeyCode::PrintScreen,
535            BareKey::Pause => KeyCode::Pause,
536            BareKey::Menu => KeyCode::Menu,
537        }
538    }
539    #[cfg(not(target_family = "wasm"))]
540    pub fn serialize_non_kitty(&self) -> Option<String> {
541        let modifiers = self.to_termwiz_modifiers();
542        let key_code_encode_modes = KeyCodeEncodeModes {
543            encoding: KeyboardEncoding::Xterm,
544            // all these flags are false because they have been dealt with before this
545            // serialization
546            application_cursor_keys: false,
547            newline_mode: false,
548            modify_other_keys: None,
549        };
550        self.to_termwiz_keycode()
551            .encode(modifiers, key_code_encode_modes, true)
552            .ok()
553    }
554    #[cfg(not(target_family = "wasm"))]
555    pub fn serialize_kitty(&self) -> Option<String> {
556        let modifiers = self.to_termwiz_modifiers();
557        let key_code_encode_modes = KeyCodeEncodeModes {
558            encoding: KeyboardEncoding::Kitty(KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES),
559            // all these flags are false because they have been dealt with before this
560            // serialization
561            application_cursor_keys: false,
562            newline_mode: false,
563            modify_other_keys: None,
564        };
565        self.to_termwiz_keycode()
566            .encode(modifiers, key_code_encode_modes, true)
567            .ok()
568    }
569    pub fn has_no_modifiers(&self) -> bool {
570        self.key_modifiers.is_empty()
571    }
572    pub fn has_modifiers(&self, modifiers: &[KeyModifier]) -> bool {
573        for modifier in modifiers {
574            if !self.key_modifiers.contains(modifier) {
575                return false;
576            }
577        }
578        true
579    }
580}
581
582#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
583pub enum Direction {
584    Left,
585    Right,
586    Up,
587    Down,
588}
589
590impl Direction {
591    pub fn invert(&self) -> Direction {
592        match *self {
593            Direction::Left => Direction::Right,
594            Direction::Down => Direction::Up,
595            Direction::Up => Direction::Down,
596            Direction::Right => Direction::Left,
597        }
598    }
599
600    pub fn is_horizontal(&self) -> bool {
601        matches!(self, Direction::Left | Direction::Right)
602    }
603
604    pub fn is_vertical(&self) -> bool {
605        matches!(self, Direction::Down | Direction::Up)
606    }
607}
608
609impl fmt::Display for Direction {
610    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611        match self {
612            Direction::Left => write!(f, "←"),
613            Direction::Right => write!(f, "→"),
614            Direction::Up => write!(f, "↑"),
615            Direction::Down => write!(f, "↓"),
616        }
617    }
618}
619
620impl FromStr for Direction {
621    type Err = String;
622    fn from_str(s: &str) -> Result<Self, Self::Err> {
623        match s {
624            "Left" | "left" => Ok(Direction::Left),
625            "Right" | "right" => Ok(Direction::Right),
626            "Up" | "up" => Ok(Direction::Up),
627            "Down" | "down" => Ok(Direction::Down),
628            _ => Err(format!(
629                "Failed to parse Direction. Unknown Direction: {}",
630                s
631            )),
632        }
633    }
634}
635
636/// Resize operation to perform.
637#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)]
638pub enum Resize {
639    Increase,
640    Decrease,
641}
642
643impl Resize {
644    pub fn invert(&self) -> Self {
645        match self {
646            Resize::Increase => Resize::Decrease,
647            Resize::Decrease => Resize::Increase,
648        }
649    }
650}
651
652impl fmt::Display for Resize {
653    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654        match self {
655            Resize::Increase => write!(f, "+"),
656            Resize::Decrease => write!(f, "-"),
657        }
658    }
659}
660
661impl FromStr for Resize {
662    type Err = String;
663    fn from_str(s: &str) -> Result<Self, Self::Err> {
664        match s {
665            "Increase" | "increase" | "+" => Ok(Resize::Increase),
666            "Decrease" | "decrease" | "-" => Ok(Resize::Decrease),
667            _ => Err(format!(
668                "failed to parse resize type. Unknown specifier '{}'",
669                s
670            )),
671        }
672    }
673}
674
675/// Container type that fully describes resize operations.
676///
677/// This is best thought of as follows:
678///
679/// - `resize` commands how the total *area* of the pane will change as part of this resize
680///   operation.
681/// - `direction` has two meanings:
682///     - `None` means to resize all borders equally
683///     - Anything else means to move the named border to achieve the change in area
684#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)]
685pub struct ResizeStrategy {
686    /// Whether to increase or resize total area
687    pub resize: Resize,
688    /// With which border, if any, to change area
689    pub direction: Option<Direction>,
690    /// If set to true (default), increasing resizes towards a viewport border will be inverted.
691    /// I.e. a scenario like this ("increase right"):
692    ///
693    /// ```text
694    /// +---+---+
695    /// |   | X |->
696    /// +---+---+
697    /// ```
698    ///
699    /// turns into this ("decrease left"):
700    ///
701    /// ```text
702    /// +---+---+
703    /// |   |-> |
704    /// +---+---+
705    /// ```
706    pub invert_on_boundaries: bool,
707}
708
709impl From<Direction> for ResizeStrategy {
710    fn from(direction: Direction) -> Self {
711        ResizeStrategy::new(Resize::Increase, Some(direction))
712    }
713}
714
715impl From<Resize> for ResizeStrategy {
716    fn from(resize: Resize) -> Self {
717        ResizeStrategy::new(resize, None)
718    }
719}
720
721impl ResizeStrategy {
722    pub fn new(resize: Resize, direction: Option<Direction>) -> Self {
723        ResizeStrategy {
724            resize,
725            direction,
726            invert_on_boundaries: true,
727        }
728    }
729
730    pub fn invert(&self) -> ResizeStrategy {
731        let resize = match self.resize {
732            Resize::Increase => Resize::Decrease,
733            Resize::Decrease => Resize::Increase,
734        };
735        let direction = match self.direction {
736            Some(direction) => Some(direction.invert()),
737            None => None,
738        };
739
740        ResizeStrategy::new(resize, direction)
741    }
742
743    pub fn resize_type(&self) -> Resize {
744        self.resize
745    }
746
747    pub fn direction(&self) -> Option<Direction> {
748        self.direction
749    }
750
751    pub fn direction_horizontal(&self) -> bool {
752        matches!(
753            self.direction,
754            Some(Direction::Left) | Some(Direction::Right)
755        )
756    }
757
758    pub fn direction_vertical(&self) -> bool {
759        matches!(self.direction, Some(Direction::Up) | Some(Direction::Down))
760    }
761
762    pub fn resize_increase(&self) -> bool {
763        self.resize == Resize::Increase
764    }
765
766    pub fn resize_decrease(&self) -> bool {
767        self.resize == Resize::Decrease
768    }
769
770    pub fn move_left_border_left(&self) -> bool {
771        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Left))
772    }
773
774    pub fn move_left_border_right(&self) -> bool {
775        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Left))
776    }
777
778    pub fn move_lower_border_down(&self) -> bool {
779        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Down))
780    }
781
782    pub fn move_lower_border_up(&self) -> bool {
783        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Down))
784    }
785
786    pub fn move_upper_border_up(&self) -> bool {
787        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Up))
788    }
789
790    pub fn move_upper_border_down(&self) -> bool {
791        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Up))
792    }
793
794    pub fn move_right_border_right(&self) -> bool {
795        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Right))
796    }
797
798    pub fn move_right_border_left(&self) -> bool {
799        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Right))
800    }
801
802    pub fn move_all_borders_out(&self) -> bool {
803        (self.resize == Resize::Increase) && (self.direction == None)
804    }
805
806    pub fn move_all_borders_in(&self) -> bool {
807        (self.resize == Resize::Decrease) && (self.direction == None)
808    }
809}
810
811impl fmt::Display for ResizeStrategy {
812    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813        let resize = match self.resize {
814            Resize::Increase => "increase",
815            Resize::Decrease => "decrease",
816        };
817        let border = match self.direction {
818            Some(Direction::Left) => "left",
819            Some(Direction::Down) => "bottom",
820            Some(Direction::Up) => "top",
821            Some(Direction::Right) => "right",
822            None => "every",
823        };
824
825        write!(f, "{} size on {} border", resize, border)
826    }
827}
828
829#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
830// FIXME: This should be extended to handle different button clicks (not just
831// left click) and the `ScrollUp` and `ScrollDown` events could probably be
832// merged into a single `Scroll(isize)` event.
833pub enum Mouse {
834    ScrollUp(usize),          // number of lines
835    ScrollDown(usize),        // number of lines
836    LeftClick(isize, usize),  // line and column
837    RightClick(isize, usize), // line and column
838    Hold(isize, usize),       // line and column
839    Release(isize, usize),    // line and column
840    Hover(isize, usize),      // line and column
841}
842
843impl Mouse {
844    pub fn position(&self) -> Option<(usize, usize)> {
845        // (line, column)
846        match self {
847            Mouse::LeftClick(line, column) => Some((*line as usize, *column as usize)),
848            Mouse::RightClick(line, column) => Some((*line as usize, *column as usize)),
849            Mouse::Hold(line, column) => Some((*line as usize, *column as usize)),
850            Mouse::Release(line, column) => Some((*line as usize, *column as usize)),
851            Mouse::Hover(line, column) => Some((*line as usize, *column as usize)),
852            _ => None,
853        }
854    }
855}
856
857#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
858pub struct FileMetadata {
859    pub is_dir: bool,
860    pub is_file: bool,
861    pub is_symlink: bool,
862    pub len: u64,
863}
864
865impl From<Metadata> for FileMetadata {
866    fn from(metadata: Metadata) -> Self {
867        FileMetadata {
868            is_dir: metadata.is_dir(),
869            is_file: metadata.is_file(),
870            is_symlink: metadata.is_symlink(),
871            len: metadata.len(),
872        }
873    }
874}
875
876/// These events can be subscribed to with subscribe method exported by `zellij-tile`.
877/// Once subscribed to, they will trigger the `update` method of the `ZellijPlugin` trait.
878#[derive(Debug, Clone, PartialEq, EnumDiscriminants, ToString, Serialize, Deserialize)]
879#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
880#[strum_discriminants(name(EventType))]
881#[non_exhaustive]
882pub enum Event {
883    ModeUpdate(ModeInfo),
884    TabUpdate(Vec<TabInfo>),
885    PaneUpdate(PaneManifest),
886    /// A key was pressed while the user is focused on this plugin's pane
887    Key(KeyWithModifier),
888    /// A mouse event happened while the user is focused on this plugin's pane
889    Mouse(Mouse),
890    /// A timer expired set by the `set_timeout` method exported by `zellij-tile`.
891    Timer(f64),
892    /// Text was copied to the clipboard anywhere in the app
893    CopyToClipboard(CopyDestination),
894    /// Failed to copy text to clipboard anywhere in the app
895    SystemClipboardFailure,
896    /// Input was received anywhere in the app
897    InputReceived,
898    /// This plugin became visible or invisible
899    Visible(bool),
900    /// A message from one of the plugin's workers
901    CustomMessage(
902        String, // message
903        String, // payload
904    ),
905    /// A file was created somewhere in the Zellij CWD folder
906    FileSystemCreate(Vec<(PathBuf, Option<FileMetadata>)>),
907    /// A file was accessed somewhere in the Zellij CWD folder
908    FileSystemRead(Vec<(PathBuf, Option<FileMetadata>)>),
909    /// A file was modified somewhere in the Zellij CWD folder
910    FileSystemUpdate(Vec<(PathBuf, Option<FileMetadata>)>),
911    /// A file was deleted somewhere in the Zellij CWD folder
912    FileSystemDelete(Vec<(PathBuf, Option<FileMetadata>)>),
913    /// A Result of plugin permission request
914    PermissionRequestResult(PermissionStatus),
915    SessionUpdate(
916        Vec<SessionInfo>,
917        Vec<(String, Duration)>, // resurrectable sessions
918    ),
919    RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR,
920    // context
921    WebRequestResult(
922        u16,
923        BTreeMap<String, String>,
924        Vec<u8>,
925        BTreeMap<String, String>,
926    ), // status,
927    // headers,
928    // body,
929    // context
930    CommandPaneOpened(u32, Context), // u32 - terminal_pane_id
931    CommandPaneExited(u32, Option<i32>, Context), // u32 - terminal_pane_id, Option<i32> -
932    // exit_code
933    PaneClosed(PaneId),
934    EditPaneOpened(u32, Context),              // u32 - terminal_pane_id
935    EditPaneExited(u32, Option<i32>, Context), // u32 - terminal_pane_id, Option<i32> - exit code
936    CommandPaneReRun(u32, Context),            // u32 - terminal_pane_id, Option<i32> -
937    FailedToWriteConfigToDisk(Option<String>), // String -> the file path we failed to write
938    ListClients(Vec<ClientInfo>),
939    HostFolderChanged(PathBuf),               // PathBuf -> new host folder
940    FailedToChangeHostFolder(Option<String>), // String -> the error we got when changing
941    PastedText(String),
942    ConfigWasWrittenToDisk,
943    WebServerStatus(WebServerStatus),
944    FailedToStartWebServer(String),
945    BeforeClose,
946    InterceptedKeyPress(KeyWithModifier),
947}
948
949#[derive(Debug, Clone, PartialEq, Eq, EnumDiscriminants, ToString, Serialize, Deserialize)]
950pub enum WebServerStatus {
951    Online(String), // String -> base url
952    Offline,
953    DifferentVersion(String), // version
954}
955
956#[derive(
957    Debug,
958    PartialEq,
959    Eq,
960    Hash,
961    Copy,
962    Clone,
963    EnumDiscriminants,
964    ToString,
965    Serialize,
966    Deserialize,
967    PartialOrd,
968    Ord,
969)]
970#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize, Display, PartialOrd, Ord))]
971#[strum_discriminants(name(PermissionType))]
972#[non_exhaustive]
973pub enum Permission {
974    ReadApplicationState,
975    ChangeApplicationState,
976    OpenFiles,
977    RunCommands,
978    OpenTerminalsOrPlugins,
979    WriteToStdin,
980    WebAccess,
981    ReadCliPipes,
982    MessageAndLaunchOtherPlugins,
983    Reconfigure,
984    FullHdAccess,
985    StartWebServer,
986    InterceptInput,
987}
988
989impl PermissionType {
990    pub fn display_name(&self) -> String {
991        match self {
992            PermissionType::ReadApplicationState => {
993                "Access Zellij state (Panes, Tabs and UI)".to_owned()
994            },
995            PermissionType::ChangeApplicationState => {
996                "Change Zellij state (Panes, Tabs and UI) and run commands".to_owned()
997            },
998            PermissionType::OpenFiles => "Open files (eg. for editing)".to_owned(),
999            PermissionType::RunCommands => "Run commands".to_owned(),
1000            PermissionType::OpenTerminalsOrPlugins => "Start new terminals and plugins".to_owned(),
1001            PermissionType::WriteToStdin => "Write to standard input (STDIN)".to_owned(),
1002            PermissionType::WebAccess => "Make web requests".to_owned(),
1003            PermissionType::ReadCliPipes => "Control command line pipes and output".to_owned(),
1004            PermissionType::MessageAndLaunchOtherPlugins => {
1005                "Send messages to and launch other plugins".to_owned()
1006            },
1007            PermissionType::Reconfigure => "Change Zellij runtime configuration".to_owned(),
1008            PermissionType::FullHdAccess => "Full access to the hard-drive".to_owned(),
1009            PermissionType::StartWebServer => {
1010                "Start a local web server to serve Zellij sessions".to_owned()
1011            },
1012            PermissionType::InterceptInput => "Intercept Input (keyboard & mouse)".to_owned(),
1013        }
1014    }
1015}
1016
1017#[derive(Debug, Clone)]
1018pub struct PluginPermission {
1019    pub name: String,
1020    pub permissions: Vec<PermissionType>,
1021}
1022
1023impl PluginPermission {
1024    pub fn new(name: String, permissions: Vec<PermissionType>) -> Self {
1025        PluginPermission { name, permissions }
1026    }
1027}
1028
1029/// Describes the different input modes, which change the way that keystrokes will be interpreted.
1030#[derive(
1031    Debug,
1032    PartialEq,
1033    Eq,
1034    Hash,
1035    Copy,
1036    Clone,
1037    EnumIter,
1038    Serialize,
1039    Deserialize,
1040    ArgEnum,
1041    PartialOrd,
1042    Ord,
1043)]
1044pub enum InputMode {
1045    /// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading
1046    /// to other modes
1047    #[serde(alias = "normal")]
1048    Normal,
1049    /// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled
1050    /// except the one leading back to normal mode
1051    #[serde(alias = "locked")]
1052    Locked,
1053    /// `Resize` mode allows resizing the different existing panes.
1054    #[serde(alias = "resize")]
1055    Resize,
1056    /// `Pane` mode allows creating and closing panes, as well as moving between them.
1057    #[serde(alias = "pane")]
1058    Pane,
1059    /// `Tab` mode allows creating and closing tabs, as well as moving between them.
1060    #[serde(alias = "tab")]
1061    Tab,
1062    /// `Scroll` mode allows scrolling up and down within a pane.
1063    #[serde(alias = "scroll")]
1064    Scroll,
1065    /// `EnterSearch` mode allows for typing in the needle for a search in the scroll buffer of a pane.
1066    #[serde(alias = "entersearch")]
1067    EnterSearch,
1068    /// `Search` mode allows for searching a term in a pane (superset of `Scroll`).
1069    #[serde(alias = "search")]
1070    Search,
1071    /// `RenameTab` mode allows assigning a new name to a tab.
1072    #[serde(alias = "renametab")]
1073    RenameTab,
1074    /// `RenamePane` mode allows assigning a new name to a pane.
1075    #[serde(alias = "renamepane")]
1076    RenamePane,
1077    /// `Session` mode allows detaching sessions
1078    #[serde(alias = "session")]
1079    Session,
1080    /// `Move` mode allows moving the different existing panes within a tab
1081    #[serde(alias = "move")]
1082    Move,
1083    /// `Prompt` mode allows interacting with active prompts.
1084    #[serde(alias = "prompt")]
1085    Prompt,
1086    /// `Tmux` mode allows for basic tmux keybindings functionality
1087    #[serde(alias = "tmux")]
1088    Tmux,
1089}
1090
1091impl Default for InputMode {
1092    fn default() -> InputMode {
1093        InputMode::Normal
1094    }
1095}
1096
1097#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
1098pub enum ThemeHue {
1099    Light,
1100    Dark,
1101}
1102impl Default for ThemeHue {
1103    fn default() -> ThemeHue {
1104        ThemeHue::Dark
1105    }
1106}
1107
1108#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
1109pub enum PaletteColor {
1110    Rgb((u8, u8, u8)),
1111    EightBit(u8),
1112}
1113impl Default for PaletteColor {
1114    fn default() -> PaletteColor {
1115        PaletteColor::EightBit(0)
1116    }
1117}
1118
1119// these are used for the web client
1120impl PaletteColor {
1121    pub fn as_rgb_str(&self) -> String {
1122        let (r, g, b) = match *self {
1123            Self::Rgb((r, g, b)) => (r, g, b),
1124            Self::EightBit(c) => eightbit_to_rgb(c),
1125        };
1126        format!("rgb({}, {}, {})", r, g, b)
1127    }
1128    pub fn from_rgb_str(rgb_str: &str) -> Self {
1129        let trimmed = rgb_str.trim();
1130
1131        if !trimmed.starts_with("rgb(") || !trimmed.ends_with(')') {
1132            return Self::default();
1133        }
1134
1135        let inner = trimmed
1136            .strip_prefix("rgb(")
1137            .and_then(|s| s.strip_suffix(')'))
1138            .unwrap_or("");
1139
1140        let parts: Vec<&str> = inner.split(',').collect();
1141
1142        if parts.len() != 3 {
1143            return Self::default();
1144        }
1145
1146        let mut rgb_values = [0u8; 3];
1147        for (i, part) in parts.iter().enumerate() {
1148            if let Some(rgb_val) = rgb_values.get_mut(i) {
1149                if let Ok(parsed) = part.trim().parse::<u8>() {
1150                    *rgb_val = parsed;
1151                } else {
1152                    return Self::default();
1153                }
1154            }
1155        }
1156
1157        Self::Rgb((rgb_values[0], rgb_values[1], rgb_values[2]))
1158    }
1159}
1160
1161impl FromStr for InputMode {
1162    type Err = ConversionError;
1163
1164    fn from_str(s: &str) -> Result<Self, ConversionError> {
1165        match s {
1166            "normal" | "Normal" => Ok(InputMode::Normal),
1167            "locked" | "Locked" => Ok(InputMode::Locked),
1168            "resize" | "Resize" => Ok(InputMode::Resize),
1169            "pane" | "Pane" => Ok(InputMode::Pane),
1170            "tab" | "Tab" => Ok(InputMode::Tab),
1171            "search" | "Search" => Ok(InputMode::Search),
1172            "scroll" | "Scroll" => Ok(InputMode::Scroll),
1173            "renametab" | "RenameTab" => Ok(InputMode::RenameTab),
1174            "renamepane" | "RenamePane" => Ok(InputMode::RenamePane),
1175            "session" | "Session" => Ok(InputMode::Session),
1176            "move" | "Move" => Ok(InputMode::Move),
1177            "prompt" | "Prompt" => Ok(InputMode::Prompt),
1178            "tmux" | "Tmux" => Ok(InputMode::Tmux),
1179            "entersearch" | "Entersearch" | "EnterSearch" => Ok(InputMode::EnterSearch),
1180            e => Err(ConversionError::UnknownInputMode(e.into())),
1181        }
1182    }
1183}
1184
1185#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
1186pub enum PaletteSource {
1187    Default,
1188    Xresources,
1189}
1190impl Default for PaletteSource {
1191    fn default() -> PaletteSource {
1192        PaletteSource::Default
1193    }
1194}
1195#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
1196pub struct Palette {
1197    pub source: PaletteSource,
1198    pub theme_hue: ThemeHue,
1199    pub fg: PaletteColor,
1200    pub bg: PaletteColor,
1201    pub black: PaletteColor,
1202    pub red: PaletteColor,
1203    pub green: PaletteColor,
1204    pub yellow: PaletteColor,
1205    pub blue: PaletteColor,
1206    pub magenta: PaletteColor,
1207    pub cyan: PaletteColor,
1208    pub white: PaletteColor,
1209    pub orange: PaletteColor,
1210    pub gray: PaletteColor,
1211    pub purple: PaletteColor,
1212    pub gold: PaletteColor,
1213    pub silver: PaletteColor,
1214    pub pink: PaletteColor,
1215    pub brown: PaletteColor,
1216}
1217
1218#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
1219pub struct Style {
1220    pub colors: Styling,
1221    pub rounded_corners: bool,
1222    pub hide_session_name: bool,
1223}
1224
1225#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1226pub enum Coloration {
1227    NoStyling,
1228    Styled(StyleDeclaration),
1229}
1230
1231impl Coloration {
1232    pub fn with_fallback(&self, fallback: StyleDeclaration) -> StyleDeclaration {
1233        match &self {
1234            Coloration::NoStyling => fallback,
1235            Coloration::Styled(style) => *style,
1236        }
1237    }
1238}
1239
1240#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1241pub struct Styling {
1242    pub text_unselected: StyleDeclaration,
1243    pub text_selected: StyleDeclaration,
1244    pub ribbon_unselected: StyleDeclaration,
1245    pub ribbon_selected: StyleDeclaration,
1246    pub table_title: StyleDeclaration,
1247    pub table_cell_unselected: StyleDeclaration,
1248    pub table_cell_selected: StyleDeclaration,
1249    pub list_unselected: StyleDeclaration,
1250    pub list_selected: StyleDeclaration,
1251    pub frame_unselected: Option<StyleDeclaration>,
1252    pub frame_selected: StyleDeclaration,
1253    pub frame_highlight: StyleDeclaration,
1254    pub exit_code_success: StyleDeclaration,
1255    pub exit_code_error: StyleDeclaration,
1256    pub multiplayer_user_colors: MultiplayerColors,
1257}
1258
1259#[derive(Debug, Copy, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1260pub struct StyleDeclaration {
1261    pub base: PaletteColor,
1262    pub background: PaletteColor,
1263    pub emphasis_0: PaletteColor,
1264    pub emphasis_1: PaletteColor,
1265    pub emphasis_2: PaletteColor,
1266    pub emphasis_3: PaletteColor,
1267}
1268
1269#[derive(Debug, Copy, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1270pub struct MultiplayerColors {
1271    pub player_1: PaletteColor,
1272    pub player_2: PaletteColor,
1273    pub player_3: PaletteColor,
1274    pub player_4: PaletteColor,
1275    pub player_5: PaletteColor,
1276    pub player_6: PaletteColor,
1277    pub player_7: PaletteColor,
1278    pub player_8: PaletteColor,
1279    pub player_9: PaletteColor,
1280    pub player_10: PaletteColor,
1281}
1282
1283pub const DEFAULT_STYLES: Styling = Styling {
1284    text_unselected: StyleDeclaration {
1285        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1286        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1287        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1288        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1289        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1290        background: PaletteColor::EightBit(default_colors::GRAY),
1291    },
1292    text_selected: StyleDeclaration {
1293        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1294        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1295        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1296        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1297        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1298        background: PaletteColor::EightBit(default_colors::GRAY),
1299    },
1300    ribbon_unselected: StyleDeclaration {
1301        base: PaletteColor::EightBit(default_colors::BLACK),
1302        emphasis_0: PaletteColor::EightBit(default_colors::RED),
1303        emphasis_1: PaletteColor::EightBit(default_colors::WHITE),
1304        emphasis_2: PaletteColor::EightBit(default_colors::BLUE),
1305        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1306        background: PaletteColor::EightBit(default_colors::GRAY),
1307    },
1308    ribbon_selected: StyleDeclaration {
1309        base: PaletteColor::EightBit(default_colors::BLACK),
1310        emphasis_0: PaletteColor::EightBit(default_colors::RED),
1311        emphasis_1: PaletteColor::EightBit(default_colors::ORANGE),
1312        emphasis_2: PaletteColor::EightBit(default_colors::MAGENTA),
1313        emphasis_3: PaletteColor::EightBit(default_colors::BLUE),
1314        background: PaletteColor::EightBit(default_colors::GREEN),
1315    },
1316    exit_code_success: StyleDeclaration {
1317        base: PaletteColor::EightBit(default_colors::GREEN),
1318        emphasis_0: PaletteColor::EightBit(default_colors::CYAN),
1319        emphasis_1: PaletteColor::EightBit(default_colors::BLACK),
1320        emphasis_2: PaletteColor::EightBit(default_colors::MAGENTA),
1321        emphasis_3: PaletteColor::EightBit(default_colors::BLUE),
1322        background: PaletteColor::EightBit(default_colors::GRAY),
1323    },
1324    exit_code_error: StyleDeclaration {
1325        base: PaletteColor::EightBit(default_colors::RED),
1326        emphasis_0: PaletteColor::EightBit(default_colors::YELLOW),
1327        emphasis_1: PaletteColor::EightBit(default_colors::GOLD),
1328        emphasis_2: PaletteColor::EightBit(default_colors::SILVER),
1329        emphasis_3: PaletteColor::EightBit(default_colors::PURPLE),
1330        background: PaletteColor::EightBit(default_colors::GRAY),
1331    },
1332    frame_unselected: None,
1333    frame_selected: StyleDeclaration {
1334        base: PaletteColor::EightBit(default_colors::GREEN),
1335        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1336        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1337        emphasis_2: PaletteColor::EightBit(default_colors::MAGENTA),
1338        emphasis_3: PaletteColor::EightBit(default_colors::BROWN),
1339        background: PaletteColor::EightBit(default_colors::GRAY),
1340    },
1341    frame_highlight: StyleDeclaration {
1342        base: PaletteColor::EightBit(default_colors::ORANGE),
1343        emphasis_0: PaletteColor::EightBit(default_colors::MAGENTA),
1344        emphasis_1: PaletteColor::EightBit(default_colors::PURPLE),
1345        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1346        emphasis_3: PaletteColor::EightBit(default_colors::GREEN),
1347        background: PaletteColor::EightBit(default_colors::GREEN),
1348    },
1349    table_title: StyleDeclaration {
1350        base: PaletteColor::EightBit(default_colors::GREEN),
1351        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1352        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1353        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1354        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1355        background: PaletteColor::EightBit(default_colors::GRAY),
1356    },
1357    table_cell_unselected: StyleDeclaration {
1358        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1359        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1360        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1361        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1362        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1363        background: PaletteColor::EightBit(default_colors::GRAY),
1364    },
1365    table_cell_selected: StyleDeclaration {
1366        base: PaletteColor::EightBit(default_colors::GREEN),
1367        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1368        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1369        emphasis_2: PaletteColor::EightBit(default_colors::RED),
1370        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1371        background: PaletteColor::EightBit(default_colors::GRAY),
1372    },
1373    list_unselected: StyleDeclaration {
1374        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1375        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1376        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1377        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1378        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1379        background: PaletteColor::EightBit(default_colors::GRAY),
1380    },
1381    list_selected: StyleDeclaration {
1382        base: PaletteColor::EightBit(default_colors::GREEN),
1383        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1384        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1385        emphasis_2: PaletteColor::EightBit(default_colors::RED),
1386        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1387        background: PaletteColor::EightBit(default_colors::GRAY),
1388    },
1389    multiplayer_user_colors: MultiplayerColors {
1390        player_1: PaletteColor::EightBit(default_colors::MAGENTA),
1391        player_2: PaletteColor::EightBit(default_colors::BLUE),
1392        player_3: PaletteColor::EightBit(default_colors::PURPLE),
1393        player_4: PaletteColor::EightBit(default_colors::YELLOW),
1394        player_5: PaletteColor::EightBit(default_colors::CYAN),
1395        player_6: PaletteColor::EightBit(default_colors::GOLD),
1396        player_7: PaletteColor::EightBit(default_colors::RED),
1397        player_8: PaletteColor::EightBit(default_colors::SILVER),
1398        player_9: PaletteColor::EightBit(default_colors::PINK),
1399        player_10: PaletteColor::EightBit(default_colors::BROWN),
1400    },
1401};
1402
1403impl Default for Styling {
1404    fn default() -> Self {
1405        DEFAULT_STYLES
1406    }
1407}
1408
1409impl From<Styling> for Palette {
1410    fn from(styling: Styling) -> Self {
1411        Palette {
1412            theme_hue: ThemeHue::Dark,
1413            source: PaletteSource::Default,
1414            fg: styling.ribbon_unselected.background,
1415            bg: styling.text_unselected.background,
1416            red: styling.exit_code_error.base,
1417            green: styling.text_unselected.emphasis_2,
1418            yellow: styling.exit_code_error.emphasis_0,
1419            blue: styling.ribbon_unselected.emphasis_2,
1420            magenta: styling.text_unselected.emphasis_3,
1421            orange: styling.text_unselected.emphasis_0,
1422            cyan: styling.text_unselected.emphasis_1,
1423            black: styling.ribbon_unselected.base,
1424            white: styling.ribbon_unselected.emphasis_1,
1425            gray: styling.list_unselected.background,
1426            purple: styling.multiplayer_user_colors.player_3,
1427            gold: styling.multiplayer_user_colors.player_6,
1428            silver: styling.multiplayer_user_colors.player_8,
1429            pink: styling.multiplayer_user_colors.player_9,
1430            brown: styling.multiplayer_user_colors.player_10,
1431        }
1432    }
1433}
1434
1435impl From<Palette> for Styling {
1436    fn from(palette: Palette) -> Self {
1437        let (fg, bg) = match palette.theme_hue {
1438            ThemeHue::Light => (palette.black, palette.white),
1439            ThemeHue::Dark => (palette.white, palette.black),
1440        };
1441        Styling {
1442            text_unselected: StyleDeclaration {
1443                base: fg,
1444                emphasis_0: palette.orange,
1445                emphasis_1: palette.cyan,
1446                emphasis_2: palette.green,
1447                emphasis_3: palette.magenta,
1448                background: bg,
1449            },
1450            text_selected: StyleDeclaration {
1451                base: fg,
1452                emphasis_0: palette.orange,
1453                emphasis_1: palette.cyan,
1454                emphasis_2: palette.green,
1455                emphasis_3: palette.magenta,
1456                background: palette.bg,
1457            },
1458            ribbon_unselected: StyleDeclaration {
1459                base: palette.black,
1460                emphasis_0: palette.red,
1461                emphasis_1: palette.white,
1462                emphasis_2: palette.blue,
1463                emphasis_3: palette.magenta,
1464                background: palette.fg,
1465            },
1466            ribbon_selected: StyleDeclaration {
1467                base: palette.black,
1468                emphasis_0: palette.red,
1469                emphasis_1: palette.orange,
1470                emphasis_2: palette.magenta,
1471                emphasis_3: palette.blue,
1472                background: palette.green,
1473            },
1474            exit_code_success: StyleDeclaration {
1475                base: palette.green,
1476                emphasis_0: palette.cyan,
1477                emphasis_1: palette.black,
1478                emphasis_2: palette.magenta,
1479                emphasis_3: palette.blue,
1480                background: Default::default(),
1481            },
1482            exit_code_error: StyleDeclaration {
1483                base: palette.red,
1484                emphasis_0: palette.yellow,
1485                emphasis_1: palette.gold,
1486                emphasis_2: palette.silver,
1487                emphasis_3: palette.purple,
1488                background: Default::default(),
1489            },
1490            frame_unselected: None,
1491            frame_selected: StyleDeclaration {
1492                base: palette.green,
1493                emphasis_0: palette.orange,
1494                emphasis_1: palette.cyan,
1495                emphasis_2: palette.magenta,
1496                emphasis_3: palette.brown,
1497                background: Default::default(),
1498            },
1499            frame_highlight: StyleDeclaration {
1500                base: palette.orange,
1501                emphasis_0: palette.magenta,
1502                emphasis_1: palette.purple,
1503                emphasis_2: palette.orange,
1504                emphasis_3: palette.orange,
1505                background: Default::default(),
1506            },
1507            table_title: StyleDeclaration {
1508                base: palette.green,
1509                emphasis_0: palette.orange,
1510                emphasis_1: palette.cyan,
1511                emphasis_2: palette.green,
1512                emphasis_3: palette.magenta,
1513                background: palette.gray,
1514            },
1515            table_cell_unselected: StyleDeclaration {
1516                base: fg,
1517                emphasis_0: palette.orange,
1518                emphasis_1: palette.cyan,
1519                emphasis_2: palette.green,
1520                emphasis_3: palette.magenta,
1521                background: palette.black,
1522            },
1523            table_cell_selected: StyleDeclaration {
1524                base: fg,
1525                emphasis_0: palette.orange,
1526                emphasis_1: palette.cyan,
1527                emphasis_2: palette.green,
1528                emphasis_3: palette.magenta,
1529                background: palette.bg,
1530            },
1531            list_unselected: StyleDeclaration {
1532                base: palette.white,
1533                emphasis_0: palette.orange,
1534                emphasis_1: palette.cyan,
1535                emphasis_2: palette.green,
1536                emphasis_3: palette.magenta,
1537                background: palette.black,
1538            },
1539            list_selected: StyleDeclaration {
1540                base: palette.white,
1541                emphasis_0: palette.orange,
1542                emphasis_1: palette.cyan,
1543                emphasis_2: palette.green,
1544                emphasis_3: palette.magenta,
1545                background: palette.bg,
1546            },
1547            multiplayer_user_colors: MultiplayerColors {
1548                player_1: palette.magenta,
1549                player_2: palette.blue,
1550                player_3: palette.purple,
1551                player_4: palette.yellow,
1552                player_5: palette.cyan,
1553                player_6: palette.gold,
1554                player_7: palette.red,
1555                player_8: palette.silver,
1556                player_9: palette.pink,
1557                player_10: palette.brown,
1558            },
1559        }
1560    }
1561}
1562
1563// FIXME: Poor devs hashtable since HashTable can't derive `Default`...
1564pub type KeybindsVec = Vec<(InputMode, Vec<(KeyWithModifier, Vec<Action>)>)>;
1565
1566/// Provides information helpful in rendering the Zellij controls for UI bars
1567#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1568pub struct ModeInfo {
1569    pub mode: InputMode,
1570    pub base_mode: Option<InputMode>,
1571    pub keybinds: KeybindsVec,
1572    pub style: Style,
1573    pub capabilities: PluginCapabilities,
1574    pub session_name: Option<String>,
1575    pub editor: Option<PathBuf>,
1576    pub shell: Option<PathBuf>,
1577    pub web_clients_allowed: Option<bool>,
1578    pub web_sharing: Option<WebSharing>,
1579    pub currently_marking_pane_group: Option<bool>,
1580    pub is_web_client: Option<bool>,
1581    // note: these are only the configured ip/port that will be bound if and when the server is up
1582    pub web_server_ip: Option<IpAddr>,
1583    pub web_server_port: Option<u16>,
1584    pub web_server_capability: Option<bool>,
1585}
1586
1587impl ModeInfo {
1588    pub fn get_mode_keybinds(&self) -> Vec<(KeyWithModifier, Vec<Action>)> {
1589        self.get_keybinds_for_mode(self.mode)
1590    }
1591
1592    pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(KeyWithModifier, Vec<Action>)> {
1593        for (vec_mode, map) in &self.keybinds {
1594            if mode == *vec_mode {
1595                return map.to_vec();
1596            }
1597        }
1598        vec![]
1599    }
1600    pub fn update_keybinds(&mut self, keybinds: Keybinds) {
1601        self.keybinds = keybinds.to_keybinds_vec();
1602    }
1603    pub fn update_default_mode(&mut self, new_default_mode: InputMode) {
1604        self.base_mode = Some(new_default_mode);
1605    }
1606    pub fn update_theme(&mut self, theme: Styling) {
1607        self.style.colors = theme.into();
1608    }
1609    pub fn update_rounded_corners(&mut self, rounded_corners: bool) {
1610        self.style.rounded_corners = rounded_corners;
1611    }
1612    pub fn update_arrow_fonts(&mut self, should_support_arrow_fonts: bool) {
1613        // it is honestly quite baffling to me how "arrow_fonts: false" can mean "I support arrow
1614        // fonts", but since this is a public API... ¯\_(ツ)_/¯
1615        self.capabilities.arrow_fonts = !should_support_arrow_fonts;
1616    }
1617    pub fn update_hide_session_name(&mut self, hide_session_name: bool) {
1618        self.style.hide_session_name = hide_session_name;
1619    }
1620}
1621
1622#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
1623pub struct SessionInfo {
1624    pub name: String,
1625    pub tabs: Vec<TabInfo>,
1626    pub panes: PaneManifest,
1627    pub connected_clients: usize,
1628    pub is_current_session: bool,
1629    pub available_layouts: Vec<LayoutInfo>,
1630    pub plugins: BTreeMap<u32, PluginInfo>,
1631    pub web_clients_allowed: bool,
1632    pub web_client_count: usize,
1633    pub tab_history: BTreeMap<ClientId, Vec<usize>>,
1634}
1635
1636#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
1637pub struct PluginInfo {
1638    pub location: String,
1639    pub configuration: BTreeMap<String, String>,
1640}
1641
1642impl From<RunPlugin> for PluginInfo {
1643    fn from(run_plugin: RunPlugin) -> Self {
1644        PluginInfo {
1645            location: run_plugin.location.display(),
1646            configuration: run_plugin.configuration.inner().clone(),
1647        }
1648    }
1649}
1650
1651#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1652pub enum LayoutInfo {
1653    BuiltIn(String),
1654    File(String),
1655    Url(String),
1656    Stringified(String),
1657}
1658
1659impl LayoutInfo {
1660    pub fn name(&self) -> &str {
1661        match self {
1662            LayoutInfo::BuiltIn(name) => &name,
1663            LayoutInfo::File(name) => &name,
1664            LayoutInfo::Url(url) => &url,
1665            LayoutInfo::Stringified(layout) => &layout,
1666        }
1667    }
1668    pub fn is_builtin(&self) -> bool {
1669        match self {
1670            LayoutInfo::BuiltIn(_name) => true,
1671            LayoutInfo::File(_name) => false,
1672            LayoutInfo::Url(_url) => false,
1673            LayoutInfo::Stringified(_stringified) => false,
1674        }
1675    }
1676    pub fn from_config(
1677        layout_dir: &Option<PathBuf>,
1678        default_layout: &Option<PathBuf>,
1679    ) -> Option<Self> {
1680        match default_layout {
1681            Some(default_layout) => {
1682                if default_layout.extension().is_some() || default_layout.components().count() > 1 {
1683                    let Some(layout_dir) = layout_dir
1684                        .as_ref()
1685                        .map(|l| l.clone())
1686                        .or_else(default_layout_dir)
1687                    else {
1688                        return None;
1689                    };
1690                    Some(LayoutInfo::File(
1691                        layout_dir.join(default_layout).display().to_string(),
1692                    ))
1693                } else if default_layout.starts_with("http://")
1694                    || default_layout.starts_with("https://")
1695                {
1696                    Some(LayoutInfo::Url(default_layout.display().to_string()))
1697                } else {
1698                    Some(LayoutInfo::BuiltIn(default_layout.display().to_string()))
1699                }
1700            },
1701            None => None,
1702        }
1703    }
1704}
1705
1706use std::hash::{Hash, Hasher};
1707
1708#[allow(clippy::derive_hash_xor_eq)]
1709impl Hash for SessionInfo {
1710    fn hash<H: Hasher>(&self, state: &mut H) {
1711        self.name.hash(state);
1712    }
1713}
1714
1715impl SessionInfo {
1716    pub fn new(name: String) -> Self {
1717        SessionInfo {
1718            name,
1719            ..Default::default()
1720        }
1721    }
1722    pub fn update_tab_info(&mut self, new_tab_info: Vec<TabInfo>) {
1723        self.tabs = new_tab_info;
1724    }
1725    pub fn update_pane_info(&mut self, new_pane_info: PaneManifest) {
1726        self.panes = new_pane_info;
1727    }
1728    pub fn update_connected_clients(&mut self, new_connected_clients: usize) {
1729        self.connected_clients = new_connected_clients;
1730    }
1731    pub fn populate_plugin_list(&mut self, plugins: BTreeMap<u32, RunPlugin>) {
1732        // u32 - plugin_id
1733        let mut plugin_list = BTreeMap::new();
1734        for (plugin_id, run_plugin) in plugins {
1735            plugin_list.insert(plugin_id, run_plugin.into());
1736        }
1737        self.plugins = plugin_list;
1738    }
1739}
1740
1741/// Contains all the information for a currently opened tab.
1742#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1743pub struct TabInfo {
1744    /// The Tab's 0 indexed position
1745    pub position: usize,
1746    /// The name of the tab as it appears in the UI (if there's enough room for it)
1747    pub name: String,
1748    /// Whether this tab is focused
1749    pub active: bool,
1750    /// The number of suppressed panes this tab has
1751    pub panes_to_hide: usize,
1752    /// Whether there's one pane taking up the whole display area on this tab
1753    pub is_fullscreen_active: bool,
1754    /// Whether input sent to this tab will be synced to all panes in it
1755    pub is_sync_panes_active: bool,
1756    pub are_floating_panes_visible: bool,
1757    pub other_focused_clients: Vec<ClientId>,
1758    pub active_swap_layout_name: Option<String>,
1759    /// Whether the user manually changed the layout, moving out of the swap layout scheme
1760    pub is_swap_layout_dirty: bool,
1761    /// Row count in the viewport (including all non-ui panes, eg. will excluse the status bar)
1762    pub viewport_rows: usize,
1763    /// Column count in the viewport (including all non-ui panes, eg. will excluse the status bar)
1764    pub viewport_columns: usize,
1765    /// Row count in the display area (including all panes, will typically be larger than the
1766    /// viewport)
1767    pub display_area_rows: usize,
1768    /// Column count in the display area (including all panes, will typically be larger than the
1769    /// viewport)
1770    pub display_area_columns: usize,
1771    /// The number of selectable (eg. not the UI bars) tiled panes currently in this tab
1772    pub selectable_tiled_panes_count: usize,
1773    /// The number of selectable (eg. not the UI bars) floating panes currently in this tab
1774    pub selectable_floating_panes_count: usize,
1775}
1776
1777/// The `PaneManifest` contains a dictionary of panes, indexed by the tab position (0 indexed).
1778/// Panes include all panes in the relevant tab, including `tiled` panes, `floating` panes and
1779/// `suppressed` panes.
1780#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
1781pub struct PaneManifest {
1782    pub panes: HashMap<usize, Vec<PaneInfo>>, // usize is the tab position
1783}
1784
1785/// Contains all the information for a currently open pane
1786///
1787/// # Difference between coordinates/size and content coordinates/size
1788///
1789/// The pane basic coordinates and size (eg. `pane_x` or `pane_columns`) are the entire space taken
1790/// up by this pane - including its frame and title if it has a border.
1791///
1792/// The pane content coordinates and size (eg. `pane_content_x` or `pane_content_columns`)
1793/// represent the area taken by the pane's content, excluding its frame and title if it has a
1794/// border.
1795#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1796pub struct PaneInfo {
1797    /// The id of the pane, unique to all panes of this kind (eg. id in terminals or id in panes)
1798    pub id: u32,
1799    /// Whether this pane is a plugin (`true`) or a terminal (`false`), used along with `id` can represent a unique pane ID across
1800    /// the running session
1801    pub is_plugin: bool,
1802    /// Whether the pane is focused in its layer (tiled or floating)
1803    pub is_focused: bool,
1804    pub is_fullscreen: bool,
1805    /// Whether a pane is floating or tiled (embedded)
1806    pub is_floating: bool,
1807    /// Whether a pane is suppressed - suppressed panes are not visible to the user, but still run
1808    /// in the background
1809    pub is_suppressed: bool,
1810    /// The full title of the pane as it appears in the UI (if there is room for it)
1811    pub title: String,
1812    /// Whether a pane exited or not, note that most panes close themselves before setting this
1813    /// flag, so this is only relevant to command panes
1814    pub exited: bool,
1815    /// The exit status of a pane if it did exit and is still in the UI
1816    pub exit_status: Option<i32>,
1817    /// A "held" pane is a paused pane that is waiting for user input (eg. a command pane that
1818    /// exited and is waiting to be re-run or closed)
1819    pub is_held: bool,
1820    pub pane_x: usize,
1821    pub pane_content_x: usize,
1822    pub pane_y: usize,
1823    pub pane_content_y: usize,
1824    pub pane_rows: usize,
1825    pub pane_content_rows: usize,
1826    pub pane_columns: usize,
1827    pub pane_content_columns: usize,
1828    /// The coordinates of the cursor - if this pane is focused - relative to the pane's
1829    /// coordinates
1830    pub cursor_coordinates_in_pane: Option<(usize, usize)>, // x, y if cursor is visible
1831    /// If this is a command pane, this will show the stringified version of the command and its
1832    /// arguments
1833    pub terminal_command: Option<String>,
1834    /// The URL from which this plugin was loaded (eg. `zellij:strider` for the built-in `strider`
1835    /// plugin or `file:/path/to/my/plugin.wasm` for a local plugin)
1836    pub plugin_url: Option<String>,
1837    /// Unselectable panes are often used for UI elements that do not have direct user interaction
1838    /// (eg. the default `status-bar` or `tab-bar`).
1839    pub is_selectable: bool,
1840    /// Grouped panes (usually through an explicit user action) that are staged for a bulk action
1841    /// the index is kept track of in order to preserve the pane group order
1842    pub index_in_pane_group: BTreeMap<ClientId, usize>,
1843}
1844#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1845pub struct ClientInfo {
1846    pub client_id: ClientId,
1847    pub pane_id: PaneId,
1848    pub running_command: String,
1849    pub is_current_client: bool,
1850}
1851
1852impl ClientInfo {
1853    pub fn new(
1854        client_id: ClientId,
1855        pane_id: PaneId,
1856        running_command: String,
1857        is_current_client: bool,
1858    ) -> Self {
1859        ClientInfo {
1860            client_id,
1861            pane_id,
1862            running_command,
1863            is_current_client,
1864        }
1865    }
1866}
1867
1868#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1869pub struct PluginIds {
1870    pub plugin_id: u32,
1871    pub zellij_pid: u32,
1872    pub initial_cwd: PathBuf,
1873    pub client_id: ClientId,
1874}
1875
1876/// Tag used to identify the plugin in layout and config kdl files
1877#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
1878pub struct PluginTag(String);
1879
1880impl PluginTag {
1881    pub fn new(url: impl Into<String>) -> Self {
1882        PluginTag(url.into())
1883    }
1884}
1885
1886impl From<PluginTag> for String {
1887    fn from(tag: PluginTag) -> Self {
1888        tag.0
1889    }
1890}
1891
1892impl fmt::Display for PluginTag {
1893    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1894        write!(f, "{}", self.0)
1895    }
1896}
1897
1898#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1899pub struct PluginCapabilities {
1900    pub arrow_fonts: bool,
1901}
1902
1903impl Default for PluginCapabilities {
1904    fn default() -> PluginCapabilities {
1905        PluginCapabilities { arrow_fonts: true }
1906    }
1907}
1908
1909/// Represents a Clipboard type
1910#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1911pub enum CopyDestination {
1912    Command,
1913    Primary,
1914    System,
1915}
1916
1917#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1918pub enum PermissionStatus {
1919    Granted,
1920    Denied,
1921}
1922
1923#[derive(Debug, Default, Clone)]
1924pub struct FileToOpen {
1925    pub path: PathBuf,
1926    pub line_number: Option<usize>,
1927    pub cwd: Option<PathBuf>,
1928}
1929
1930impl FileToOpen {
1931    pub fn new<P: AsRef<Path>>(path: P) -> Self {
1932        FileToOpen {
1933            path: path.as_ref().to_path_buf(),
1934            ..Default::default()
1935        }
1936    }
1937    pub fn with_line_number(mut self, line_number: usize) -> Self {
1938        self.line_number = Some(line_number);
1939        self
1940    }
1941    pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
1942        self.cwd = Some(cwd);
1943        self
1944    }
1945}
1946
1947#[derive(Debug, Default, Clone)]
1948pub struct CommandToRun {
1949    pub path: PathBuf,
1950    pub args: Vec<String>,
1951    pub cwd: Option<PathBuf>,
1952}
1953
1954impl CommandToRun {
1955    pub fn new<P: AsRef<Path>>(path: P) -> Self {
1956        CommandToRun {
1957            path: path.as_ref().to_path_buf(),
1958            ..Default::default()
1959        }
1960    }
1961    pub fn new_with_args<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) -> Self {
1962        CommandToRun {
1963            path: path.as_ref().to_path_buf(),
1964            args: args.into_iter().map(|a| a.as_ref().to_owned()).collect(),
1965            ..Default::default()
1966        }
1967    }
1968}
1969
1970#[derive(Debug, Default, Clone)]
1971pub struct MessageToPlugin {
1972    pub plugin_url: Option<String>,
1973    pub destination_plugin_id: Option<u32>,
1974    pub plugin_config: BTreeMap<String, String>,
1975    pub message_name: String,
1976    pub message_payload: Option<String>,
1977    pub message_args: BTreeMap<String, String>,
1978    /// these will only be used in case we need to launch a new plugin to send this message to,
1979    /// since none are running
1980    pub new_plugin_args: Option<NewPluginArgs>,
1981    pub floating_pane_coordinates: Option<FloatingPaneCoordinates>,
1982}
1983
1984#[derive(Debug, Default, Clone)]
1985pub struct NewPluginArgs {
1986    pub should_float: Option<bool>,
1987    pub pane_id_to_replace: Option<PaneId>,
1988    pub pane_title: Option<String>,
1989    pub cwd: Option<PathBuf>,
1990    pub skip_cache: bool,
1991    pub should_focus: Option<bool>,
1992}
1993
1994#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
1995pub enum PaneId {
1996    Terminal(u32),
1997    Plugin(u32),
1998}
1999
2000impl FromStr for PaneId {
2001    type Err = Box<dyn std::error::Error>;
2002    fn from_str(stringified_pane_id: &str) -> Result<Self, Self::Err> {
2003        if let Some(terminal_stringified_pane_id) = stringified_pane_id.strip_prefix("terminal_") {
2004            u32::from_str_radix(terminal_stringified_pane_id, 10)
2005                .map(|id| PaneId::Terminal(id))
2006                .map_err(|e| e.into())
2007        } else if let Some(plugin_pane_id) = stringified_pane_id.strip_prefix("plugin_") {
2008            u32::from_str_radix(plugin_pane_id, 10)
2009                .map(|id| PaneId::Plugin(id))
2010                .map_err(|e| e.into())
2011        } else {
2012            u32::from_str_radix(&stringified_pane_id, 10)
2013                .map(|id| PaneId::Terminal(id))
2014                .map_err(|e| e.into())
2015        }
2016    }
2017}
2018
2019impl MessageToPlugin {
2020    pub fn new(message_name: impl Into<String>) -> Self {
2021        MessageToPlugin {
2022            message_name: message_name.into(),
2023            ..Default::default()
2024        }
2025    }
2026    pub fn with_plugin_url(mut self, url: impl Into<String>) -> Self {
2027        self.plugin_url = Some(url.into());
2028        self
2029    }
2030    pub fn with_destination_plugin_id(mut self, destination_plugin_id: u32) -> Self {
2031        self.destination_plugin_id = Some(destination_plugin_id);
2032        self
2033    }
2034    pub fn with_plugin_config(mut self, plugin_config: BTreeMap<String, String>) -> Self {
2035        self.plugin_config = plugin_config;
2036        self
2037    }
2038    pub fn with_payload(mut self, payload: impl Into<String>) -> Self {
2039        self.message_payload = Some(payload.into());
2040        self
2041    }
2042    pub fn with_args(mut self, args: BTreeMap<String, String>) -> Self {
2043        self.message_args = args;
2044        self
2045    }
2046    pub fn with_floating_pane_coordinates(
2047        mut self,
2048        floating_pane_coordinates: FloatingPaneCoordinates,
2049    ) -> Self {
2050        self.floating_pane_coordinates = Some(floating_pane_coordinates);
2051        self
2052    }
2053    pub fn new_plugin_instance_should_float(mut self, should_float: bool) -> Self {
2054        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2055        new_plugin_args.should_float = Some(should_float);
2056        self
2057    }
2058    pub fn new_plugin_instance_should_replace_pane(mut self, pane_id: PaneId) -> Self {
2059        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2060        new_plugin_args.pane_id_to_replace = Some(pane_id);
2061        self
2062    }
2063    pub fn new_plugin_instance_should_have_pane_title(
2064        mut self,
2065        pane_title: impl Into<String>,
2066    ) -> Self {
2067        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2068        new_plugin_args.pane_title = Some(pane_title.into());
2069        self
2070    }
2071    pub fn new_plugin_instance_should_have_cwd(mut self, cwd: PathBuf) -> Self {
2072        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2073        new_plugin_args.cwd = Some(cwd);
2074        self
2075    }
2076    pub fn new_plugin_instance_should_skip_cache(mut self) -> Self {
2077        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2078        new_plugin_args.skip_cache = true;
2079        self
2080    }
2081    pub fn new_plugin_instance_should_be_focused(mut self) -> Self {
2082        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2083        new_plugin_args.should_focus = Some(true);
2084        self
2085    }
2086}
2087
2088#[derive(Debug, Default, Clone, Serialize, Deserialize)]
2089pub struct ConnectToSession {
2090    pub name: Option<String>,
2091    pub tab_position: Option<usize>,
2092    pub pane_id: Option<(u32, bool)>, // (id, is_plugin)
2093    pub layout: Option<LayoutInfo>,
2094    pub cwd: Option<PathBuf>,
2095}
2096
2097impl ConnectToSession {
2098    pub fn apply_layout_dir(&mut self, layout_dir: &PathBuf) {
2099        if let Some(LayoutInfo::File(file_path)) = self.layout.as_mut() {
2100            *file_path = Path::join(layout_dir, &file_path)
2101                .to_string_lossy()
2102                .to_string();
2103        }
2104    }
2105}
2106
2107#[derive(Debug, Default, Clone)]
2108pub struct PluginMessage {
2109    pub name: String,
2110    pub payload: String,
2111    pub worker_name: Option<String>,
2112}
2113
2114impl PluginMessage {
2115    pub fn new_to_worker(worker_name: &str, message: &str, payload: &str) -> Self {
2116        PluginMessage {
2117            name: message.to_owned(),
2118            payload: payload.to_owned(),
2119            worker_name: Some(worker_name.to_owned()),
2120        }
2121    }
2122    pub fn new_to_plugin(message: &str, payload: &str) -> Self {
2123        PluginMessage {
2124            name: message.to_owned(),
2125            payload: payload.to_owned(),
2126            worker_name: None,
2127        }
2128    }
2129}
2130
2131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2132pub enum HttpVerb {
2133    Get,
2134    Post,
2135    Put,
2136    Delete,
2137}
2138
2139#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2140pub enum PipeSource {
2141    Cli(String), // String is the pipe_id of the CLI pipe (used for blocking/unblocking)
2142    Plugin(u32), // u32 is the lugin id
2143    Keybind,     // TODO: consider including the actual keybind here?
2144}
2145
2146#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2147pub struct PipeMessage {
2148    pub source: PipeSource,
2149    pub name: String,
2150    pub payload: Option<String>,
2151    pub args: BTreeMap<String, String>,
2152    pub is_private: bool,
2153}
2154
2155impl PipeMessage {
2156    pub fn new(
2157        source: PipeSource,
2158        name: impl Into<String>,
2159        payload: &Option<String>,
2160        args: &Option<BTreeMap<String, String>>,
2161        is_private: bool,
2162    ) -> Self {
2163        PipeMessage {
2164            source,
2165            name: name.into(),
2166            payload: payload.clone(),
2167            args: args.clone().unwrap_or_else(|| Default::default()),
2168            is_private,
2169        }
2170    }
2171}
2172
2173#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2174pub struct FloatingPaneCoordinates {
2175    pub x: Option<SplitSize>,
2176    pub y: Option<SplitSize>,
2177    pub width: Option<SplitSize>,
2178    pub height: Option<SplitSize>,
2179    pub pinned: Option<bool>,
2180}
2181
2182impl FloatingPaneCoordinates {
2183    pub fn new(
2184        x: Option<String>,
2185        y: Option<String>,
2186        width: Option<String>,
2187        height: Option<String>,
2188        pinned: Option<bool>,
2189    ) -> Option<Self> {
2190        let x = x.and_then(|x| SplitSize::from_str(&x).ok());
2191        let y = y.and_then(|y| SplitSize::from_str(&y).ok());
2192        let width = width.and_then(|width| SplitSize::from_str(&width).ok());
2193        let height = height.and_then(|height| SplitSize::from_str(&height).ok());
2194        if x.is_none() && y.is_none() && width.is_none() && height.is_none() && pinned.is_none() {
2195            None
2196        } else {
2197            Some(FloatingPaneCoordinates {
2198                x,
2199                y,
2200                width,
2201                height,
2202                pinned,
2203            })
2204        }
2205    }
2206    pub fn with_x_fixed(mut self, x: usize) -> Self {
2207        self.x = Some(SplitSize::Fixed(x));
2208        self
2209    }
2210    pub fn with_x_percent(mut self, x: usize) -> Self {
2211        if x > 100 {
2212            eprintln!("x must be between 0 and 100");
2213            return self;
2214        }
2215        self.x = Some(SplitSize::Percent(x));
2216        self
2217    }
2218    pub fn with_y_fixed(mut self, y: usize) -> Self {
2219        self.y = Some(SplitSize::Fixed(y));
2220        self
2221    }
2222    pub fn with_y_percent(mut self, y: usize) -> Self {
2223        if y > 100 {
2224            eprintln!("y must be between 0 and 100");
2225            return self;
2226        }
2227        self.y = Some(SplitSize::Percent(y));
2228        self
2229    }
2230    pub fn with_width_fixed(mut self, width: usize) -> Self {
2231        self.width = Some(SplitSize::Fixed(width));
2232        self
2233    }
2234    pub fn with_width_percent(mut self, width: usize) -> Self {
2235        if width > 100 {
2236            eprintln!("width must be between 0 and 100");
2237            return self;
2238        }
2239        self.width = Some(SplitSize::Percent(width));
2240        self
2241    }
2242    pub fn with_height_fixed(mut self, height: usize) -> Self {
2243        self.height = Some(SplitSize::Fixed(height));
2244        self
2245    }
2246    pub fn with_height_percent(mut self, height: usize) -> Self {
2247        if height > 100 {
2248            eprintln!("height must be between 0 and 100");
2249            return self;
2250        }
2251        self.height = Some(SplitSize::Percent(height));
2252        self
2253    }
2254}
2255
2256impl From<PaneGeom> for FloatingPaneCoordinates {
2257    fn from(pane_geom: PaneGeom) -> Self {
2258        FloatingPaneCoordinates {
2259            x: Some(SplitSize::Fixed(pane_geom.x)),
2260            y: Some(SplitSize::Fixed(pane_geom.y)),
2261            width: Some(SplitSize::Fixed(pane_geom.cols.as_usize())),
2262            height: Some(SplitSize::Fixed(pane_geom.rows.as_usize())),
2263            pinned: Some(pane_geom.is_pinned),
2264        }
2265    }
2266}
2267
2268#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2269pub struct OriginatingPlugin {
2270    pub plugin_id: u32,
2271    pub client_id: ClientId,
2272    pub context: Context,
2273}
2274
2275impl OriginatingPlugin {
2276    pub fn new(plugin_id: u32, client_id: ClientId, context: Context) -> Self {
2277        OriginatingPlugin {
2278            plugin_id,
2279            client_id,
2280            context,
2281        }
2282    }
2283}
2284
2285#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
2286pub enum WebSharing {
2287    #[serde(alias = "on")]
2288    On,
2289    #[serde(alias = "off")]
2290    Off,
2291    #[serde(alias = "disabled")]
2292    Disabled,
2293}
2294
2295impl Default for WebSharing {
2296    fn default() -> Self {
2297        Self::Off
2298    }
2299}
2300
2301impl WebSharing {
2302    pub fn is_on(&self) -> bool {
2303        match self {
2304            WebSharing::On => true,
2305            _ => false,
2306        }
2307    }
2308    pub fn web_clients_allowed(&self) -> bool {
2309        match self {
2310            WebSharing::On => true,
2311            _ => false,
2312        }
2313    }
2314    pub fn sharing_is_disabled(&self) -> bool {
2315        match self {
2316            WebSharing::Disabled => true,
2317            _ => false,
2318        }
2319    }
2320    pub fn set_sharing(&mut self) -> bool {
2321        // returns true if successfully set sharing
2322        match self {
2323            WebSharing::On => true,
2324            WebSharing::Off => {
2325                *self = WebSharing::On;
2326                true
2327            },
2328            WebSharing::Disabled => false,
2329        }
2330    }
2331    pub fn set_not_sharing(&mut self) -> bool {
2332        // returns true if successfully set not sharing
2333        match self {
2334            WebSharing::On => {
2335                *self = WebSharing::Off;
2336                true
2337            },
2338            WebSharing::Off => true,
2339            WebSharing::Disabled => false,
2340        }
2341    }
2342}
2343
2344impl FromStr for WebSharing {
2345    type Err = String;
2346    fn from_str(s: &str) -> Result<Self, Self::Err> {
2347        match s {
2348            "On" | "on" => Ok(Self::On),
2349            "Off" | "off" => Ok(Self::Off),
2350            "Disabled" | "disabled" => Ok(Self::Disabled),
2351            _ => Err(format!("No such option: {}", s)),
2352        }
2353    }
2354}
2355
2356type Context = BTreeMap<String, String>;
2357
2358#[derive(Debug, Clone, EnumDiscriminants, ToString)]
2359#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
2360#[strum_discriminants(name(CommandType))]
2361pub enum PluginCommand {
2362    Subscribe(HashSet<EventType>),
2363    Unsubscribe(HashSet<EventType>),
2364    SetSelectable(bool),
2365    GetPluginIds,
2366    GetZellijVersion,
2367    OpenFile(FileToOpen, Context),
2368    OpenFileFloating(FileToOpen, Option<FloatingPaneCoordinates>, Context),
2369    OpenTerminal(FileToOpen), // only used for the path as cwd
2370    OpenTerminalFloating(FileToOpen, Option<FloatingPaneCoordinates>), // only used for the path as cwd
2371    OpenCommandPane(CommandToRun, Context),
2372    OpenCommandPaneFloating(CommandToRun, Option<FloatingPaneCoordinates>, Context),
2373    SwitchTabTo(u32), // tab index
2374    SetTimeout(f64),  // seconds
2375    ExecCmd(Vec<String>),
2376    PostMessageTo(PluginMessage),
2377    PostMessageToPlugin(PluginMessage),
2378    HideSelf,
2379    ShowSelf(bool), // bool - should float if hidden
2380    SwitchToMode(InputMode),
2381    NewTabsWithLayout(String), // raw kdl layout
2382    NewTab {
2383        name: Option<String>,
2384        cwd: Option<String>,
2385    },
2386    GoToNextTab,
2387    GoToPreviousTab,
2388    Resize(Resize),
2389    ResizeWithDirection(ResizeStrategy),
2390    FocusNextPane,
2391    FocusPreviousPane,
2392    MoveFocus(Direction),
2393    MoveFocusOrTab(Direction),
2394    Detach,
2395    EditScrollback,
2396    Write(Vec<u8>), // bytes
2397    WriteChars(String),
2398    ToggleTab,
2399    MovePane,
2400    MovePaneWithDirection(Direction),
2401    ClearScreen,
2402    ScrollUp,
2403    ScrollDown,
2404    ScrollToTop,
2405    ScrollToBottom,
2406    PageScrollUp,
2407    PageScrollDown,
2408    ToggleFocusFullscreen,
2409    TogglePaneFrames,
2410    TogglePaneEmbedOrEject,
2411    UndoRenamePane,
2412    CloseFocus,
2413    ToggleActiveTabSync,
2414    CloseFocusedTab,
2415    UndoRenameTab,
2416    QuitZellij,
2417    PreviousSwapLayout,
2418    NextSwapLayout,
2419    GoToTabName(String),
2420    FocusOrCreateTab(String),
2421    GoToTab(u32),                    // tab index
2422    StartOrReloadPlugin(String),     // plugin url (eg. file:/path/to/plugin.wasm)
2423    CloseTerminalPane(u32),          // terminal pane id
2424    ClosePluginPane(u32),            // plugin pane id
2425    FocusTerminalPane(u32, bool),    // terminal pane id, should_float_if_hidden
2426    FocusPluginPane(u32, bool),      // plugin pane id, should_float_if_hidden
2427    RenameTerminalPane(u32, String), // terminal pane id, new name
2428    RenamePluginPane(u32, String),   // plugin pane id, new name
2429    RenameTab(u32, String),          // tab index, new name
2430    ReportPanic(String),             // stringified panic
2431    RequestPluginPermissions(Vec<PermissionType>),
2432    SwitchSession(ConnectToSession),
2433    DeleteDeadSession(String),       // String -> session name
2434    DeleteAllDeadSessions,           // String -> session name
2435    OpenTerminalInPlace(FileToOpen), // only used for the path as cwd
2436    OpenFileInPlace(FileToOpen, Context),
2437    OpenCommandPaneInPlace(CommandToRun, Context),
2438    RunCommand(
2439        Vec<String>,              // command
2440        BTreeMap<String, String>, // env_variables
2441        PathBuf,                  // cwd
2442        BTreeMap<String, String>, // context
2443    ),
2444    WebRequest(
2445        String, // url
2446        HttpVerb,
2447        BTreeMap<String, String>, // headers
2448        Vec<u8>,                  // body
2449        BTreeMap<String, String>, // context
2450    ),
2451    RenameSession(String),         // String -> new session name
2452    UnblockCliPipeInput(String),   // String => pipe name
2453    BlockCliPipeInput(String),     // String => pipe name
2454    CliPipeOutput(String, String), // String => pipe name, String => output
2455    MessageToPlugin(MessageToPlugin),
2456    DisconnectOtherClients,
2457    KillSessions(Vec<String>), // one or more session names
2458    ScanHostFolder(PathBuf),   // TODO: rename to ScanHostFolder
2459    WatchFilesystem,
2460    DumpSessionLayout,
2461    CloseSelf,
2462    NewTabsWithLayoutInfo(LayoutInfo),
2463    Reconfigure(String, bool), // String -> stringified configuration, bool -> save configuration
2464    // file to disk
2465    HidePaneWithId(PaneId),
2466    ShowPaneWithId(PaneId, bool), // bool -> should_float_if_hidden
2467    OpenCommandPaneBackground(CommandToRun, Context),
2468    RerunCommandPane(u32), // u32  - terminal pane id
2469    ResizePaneIdWithDirection(ResizeStrategy, PaneId),
2470    EditScrollbackForPaneWithId(PaneId),
2471    WriteToPaneId(Vec<u8>, PaneId),
2472    WriteCharsToPaneId(String, PaneId),
2473    MovePaneWithPaneId(PaneId),
2474    MovePaneWithPaneIdInDirection(PaneId, Direction),
2475    ClearScreenForPaneId(PaneId),
2476    ScrollUpInPaneId(PaneId),
2477    ScrollDownInPaneId(PaneId),
2478    ScrollToTopInPaneId(PaneId),
2479    ScrollToBottomInPaneId(PaneId),
2480    PageScrollUpInPaneId(PaneId),
2481    PageScrollDownInPaneId(PaneId),
2482    TogglePaneIdFullscreen(PaneId),
2483    TogglePaneEmbedOrEjectForPaneId(PaneId),
2484    CloseTabWithIndex(usize), // usize - tab_index
2485    BreakPanesToNewTab(Vec<PaneId>, Option<String>, bool), // bool -
2486    // should_change_focus_to_new_tab,
2487    // Option<String> - optional name for
2488    // the new tab
2489    BreakPanesToTabWithIndex(Vec<PaneId>, usize, bool), // usize - tab_index, bool -
2490    // should_change_focus_to_new_tab
2491    ReloadPlugin(u32), // u32 - plugin pane id
2492    LoadNewPlugin {
2493        url: String,
2494        config: BTreeMap<String, String>,
2495        load_in_background: bool,
2496        skip_plugin_cache: bool,
2497    },
2498    RebindKeys {
2499        keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
2500        keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
2501        write_config_to_disk: bool,
2502    },
2503    ListClients,
2504    ChangeHostFolder(PathBuf),
2505    SetFloatingPanePinned(PaneId, bool), // bool -> should be pinned
2506    StackPanes(Vec<PaneId>),
2507    ChangeFloatingPanesCoordinates(Vec<(PaneId, FloatingPaneCoordinates)>),
2508    OpenCommandPaneNearPlugin(CommandToRun, Context),
2509    OpenTerminalNearPlugin(FileToOpen),
2510    OpenTerminalFloatingNearPlugin(FileToOpen, Option<FloatingPaneCoordinates>),
2511    OpenTerminalInPlaceOfPlugin(FileToOpen, bool), // bool -> close_plugin_after_replace
2512    OpenCommandPaneFloatingNearPlugin(CommandToRun, Option<FloatingPaneCoordinates>, Context),
2513    OpenCommandPaneInPlaceOfPlugin(CommandToRun, bool, Context), // bool ->
2514    // close_plugin_after_replace
2515    OpenFileNearPlugin(FileToOpen, Context),
2516    OpenFileFloatingNearPlugin(FileToOpen, Option<FloatingPaneCoordinates>, Context),
2517    StartWebServer,
2518    StopWebServer,
2519    ShareCurrentSession,
2520    StopSharingCurrentSession,
2521    OpenFileInPlaceOfPlugin(FileToOpen, bool, Context), // bool -> close_plugin_after_replace
2522    GroupAndUngroupPanes(Vec<PaneId>, Vec<PaneId>, bool), // panes to group, panes to ungroup,
2523    // bool -> for all clients
2524    HighlightAndUnhighlightPanes(Vec<PaneId>, Vec<PaneId>), // panes to highlight, panes to
2525    // unhighlight
2526    CloseMultiplePanes(Vec<PaneId>),
2527    FloatMultiplePanes(Vec<PaneId>),
2528    EmbedMultiplePanes(Vec<PaneId>),
2529    QueryWebServerStatus,
2530    SetSelfMouseSelectionSupport(bool),
2531    GenerateWebLoginToken(Option<String>), // String -> optional token label
2532    RevokeWebLoginToken(String),           // String -> token id (provided name or generated id)
2533    ListWebLoginTokens,
2534    RevokeAllWebLoginTokens,
2535    RenameWebLoginToken(String, String), // (original_name, new_name)
2536    InterceptKeyPresses,
2537    ClearKeyPressesIntercepts,
2538    ReplacePaneWithExistingPane(PaneId, PaneId), // (pane id to replace, pane id of existing)
2539}