Skip to main content

zellij_utils/
data.rs

1use crate::home::default_layout_dir;
2use crate::input::actions::{Action, RunCommandAction};
3use crate::input::config::{ConversionError, KdlError};
4use crate::input::keybinds::Keybinds;
5use crate::input::layout::{
6    Layout, PercentOrFixed, Run, RunPlugin, RunPluginLocation, RunPluginOrAlias,
7};
8use crate::pane_size::PaneGeom;
9use crate::position::Position;
10use crate::shared::{colors as default_colors, eightbit_to_rgb};
11use clap::ArgEnum;
12use serde::{Deserialize, Serialize};
13use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
14use std::fmt;
15use std::fs::Metadata;
16use std::hash::{Hash, Hasher};
17use std::net::IpAddr;
18use std::path::{Path, PathBuf};
19use std::str::{self, FromStr};
20use std::time::Duration;
21use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString};
22use unicode_width::UnicodeWidthChar;
23
24#[cfg(not(target_family = "wasm"))]
25use crate::vendored::termwiz::{
26    input::KittyKeyboardFlags,
27    input::{KeyCode, KeyCodeEncodeModes, KeyboardEncoding, Modifiers},
28};
29
30pub type ClientId = u16; // TODO: merge with crate type?
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33pub enum UnblockCondition {
34    /// Unblock only when exit status is 0 (success)
35    OnExitSuccess,
36    /// Unblock only when exit status is non-zero (failure)
37    OnExitFailure,
38    /// Unblock on any exit (success or failure)
39    OnAnyExit,
40}
41
42impl UnblockCondition {
43    /// Check if the condition is met for the given exit status
44    pub fn is_met(&self, exit_status: i32) -> bool {
45        match self {
46            UnblockCondition::OnExitSuccess => exit_status == 0,
47            UnblockCondition::OnExitFailure => exit_status != 0,
48            UnblockCondition::OnAnyExit => true,
49        }
50    }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub enum CommandOrPlugin {
55    Command(RunCommandAction),
56    Plugin(RunPluginOrAlias),
57    File(FileToOpen), // open file in configured editor
58}
59
60impl CommandOrPlugin {
61    pub fn new_command(command: Vec<String>) -> Self {
62        CommandOrPlugin::Command(RunCommandAction::new(command))
63    }
64}
65
66pub fn client_id_to_colors(
67    client_id: ClientId,
68    colors: MultiplayerColors,
69) -> Option<(PaletteColor, PaletteColor)> {
70    // (primary color, secondary color)
71    let black = PaletteColor::EightBit(default_colors::BLACK);
72    match client_id {
73        1 => Some((colors.player_1, black)),
74        2 => Some((colors.player_2, black)),
75        3 => Some((colors.player_3, black)),
76        4 => Some((colors.player_4, black)),
77        5 => Some((colors.player_5, black)),
78        6 => Some((colors.player_6, black)),
79        7 => Some((colors.player_7, black)),
80        8 => Some((colors.player_8, black)),
81        9 => Some((colors.player_9, black)),
82        10 => Some((colors.player_10, black)),
83        _ => None,
84    }
85}
86
87pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) {
88    (colors.green, colors.black)
89}
90
91impl FromStr for KeyWithModifier {
92    type Err = Box<dyn std::error::Error>;
93    fn from_str(key_str: &str) -> Result<Self, Self::Err> {
94        let mut key_string_parts: Vec<&str> = key_str.split_ascii_whitespace().collect();
95        let bare_key: BareKey = BareKey::from_str(key_string_parts.pop().ok_or("empty key")?)?;
96        let mut key_modifiers: BTreeSet<KeyModifier> = BTreeSet::new();
97        for stringified_modifier in key_string_parts {
98            key_modifiers.insert(KeyModifier::from_str(stringified_modifier)?);
99        }
100        Ok(KeyWithModifier {
101            bare_key,
102            key_modifiers,
103        })
104    }
105}
106
107#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialOrd, Ord)]
108pub struct KeyWithModifier {
109    pub bare_key: BareKey,
110    pub key_modifiers: BTreeSet<KeyModifier>,
111}
112
113impl PartialEq for KeyWithModifier {
114    fn eq(&self, other: &Self) -> bool {
115        match (self.bare_key, other.bare_key) {
116            (BareKey::Char(self_char), BareKey::Char(other_char))
117                if self_char.to_ascii_lowercase() == other_char.to_ascii_lowercase() =>
118            {
119                let mut self_cloned = self.clone();
120                let mut other_cloned = other.clone();
121                if self_char.is_ascii_uppercase() {
122                    self_cloned.bare_key = BareKey::Char(self_char.to_ascii_lowercase());
123                    self_cloned.key_modifiers.insert(KeyModifier::Shift);
124                }
125                if other_char.is_ascii_uppercase() {
126                    other_cloned.bare_key = BareKey::Char(self_char.to_ascii_lowercase());
127                    other_cloned.key_modifiers.insert(KeyModifier::Shift);
128                }
129                self_cloned.bare_key == other_cloned.bare_key
130                    && self_cloned.key_modifiers == other_cloned.key_modifiers
131            },
132            _ => self.bare_key == other.bare_key && self.key_modifiers == other.key_modifiers,
133        }
134    }
135}
136
137impl Hash for KeyWithModifier {
138    fn hash<H: Hasher>(&self, state: &mut H) {
139        match self.bare_key {
140            BareKey::Char(character) if character.is_ascii_uppercase() => {
141                let mut to_hash = self.clone();
142                to_hash.bare_key = BareKey::Char(character.to_ascii_lowercase());
143                to_hash.key_modifiers.insert(KeyModifier::Shift);
144                to_hash.bare_key.hash(state);
145                to_hash.key_modifiers.hash(state);
146            },
147            _ => {
148                self.bare_key.hash(state);
149                self.key_modifiers.hash(state);
150            },
151        }
152    }
153}
154
155impl fmt::Display for KeyWithModifier {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        if self.key_modifiers.is_empty() {
158            write!(f, "{}", self.bare_key)
159        } else {
160            write!(
161                f,
162                "{} {}",
163                self.key_modifiers
164                    .iter()
165                    .map(|m| m.to_string())
166                    .collect::<Vec<_>>()
167                    .join(" "),
168                self.bare_key
169            )
170        }
171    }
172}
173
174#[cfg(not(target_family = "wasm"))]
175impl Into<Modifiers> for &KeyModifier {
176    fn into(self) -> Modifiers {
177        match self {
178            KeyModifier::Shift => Modifiers::SHIFT,
179            KeyModifier::Alt => Modifiers::ALT,
180            KeyModifier::Ctrl => Modifiers::CTRL,
181            KeyModifier::Super => Modifiers::SUPER,
182        }
183    }
184}
185
186#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
187pub enum BareKey {
188    PageDown,
189    PageUp,
190    Left,
191    Down,
192    Up,
193    Right,
194    Home,
195    End,
196    Backspace,
197    Delete,
198    Insert,
199    F(u8),
200    Char(char),
201    Tab,
202    Esc,
203    Enter,
204    CapsLock,
205    ScrollLock,
206    NumLock,
207    PrintScreen,
208    Pause,
209    Menu,
210}
211
212impl fmt::Display for BareKey {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        match self {
215            BareKey::PageDown => write!(f, "PgDn"),
216            BareKey::PageUp => write!(f, "PgUp"),
217            BareKey::Left => write!(f, "←"),
218            BareKey::Down => write!(f, "↓"),
219            BareKey::Up => write!(f, "↑"),
220            BareKey::Right => write!(f, "→"),
221            BareKey::Home => write!(f, "HOME"),
222            BareKey::End => write!(f, "END"),
223            BareKey::Backspace => write!(f, "BACKSPACE"),
224            BareKey::Delete => write!(f, "DEL"),
225            BareKey::Insert => write!(f, "INS"),
226            BareKey::F(index) => write!(f, "F{}", index),
227            BareKey::Char(' ') => write!(f, "SPACE"),
228            BareKey::Char(character) => write!(f, "{}", character),
229            BareKey::Tab => write!(f, "TAB"),
230            BareKey::Esc => write!(f, "ESC"),
231            BareKey::Enter => write!(f, "ENTER"),
232            BareKey::CapsLock => write!(f, "CAPSlOCK"),
233            BareKey::ScrollLock => write!(f, "SCROLLlOCK"),
234            BareKey::NumLock => write!(f, "NUMLOCK"),
235            BareKey::PrintScreen => write!(f, "PRINTSCREEN"),
236            BareKey::Pause => write!(f, "PAUSE"),
237            BareKey::Menu => write!(f, "MENU"),
238        }
239    }
240}
241
242impl FromStr for BareKey {
243    type Err = Box<dyn std::error::Error>;
244    fn from_str(key_str: &str) -> Result<Self, Self::Err> {
245        match key_str.to_ascii_lowercase().as_str() {
246            "pagedown" => Ok(BareKey::PageDown),
247            "pageup" => Ok(BareKey::PageUp),
248            "left" => Ok(BareKey::Left),
249            "down" => Ok(BareKey::Down),
250            "up" => Ok(BareKey::Up),
251            "right" => Ok(BareKey::Right),
252            "home" => Ok(BareKey::Home),
253            "end" => Ok(BareKey::End),
254            "backspace" => Ok(BareKey::Backspace),
255            "delete" => Ok(BareKey::Delete),
256            "insert" => Ok(BareKey::Insert),
257            "f1" => Ok(BareKey::F(1)),
258            "f2" => Ok(BareKey::F(2)),
259            "f3" => Ok(BareKey::F(3)),
260            "f4" => Ok(BareKey::F(4)),
261            "f5" => Ok(BareKey::F(5)),
262            "f6" => Ok(BareKey::F(6)),
263            "f7" => Ok(BareKey::F(7)),
264            "f8" => Ok(BareKey::F(8)),
265            "f9" => Ok(BareKey::F(9)),
266            "f10" => Ok(BareKey::F(10)),
267            "f11" => Ok(BareKey::F(11)),
268            "f12" => Ok(BareKey::F(12)),
269            "tab" => Ok(BareKey::Tab),
270            "esc" => Ok(BareKey::Esc),
271            "enter" => Ok(BareKey::Enter),
272            "capslock" => Ok(BareKey::CapsLock),
273            "scrolllock" => Ok(BareKey::ScrollLock),
274            "numlock" => Ok(BareKey::NumLock),
275            "printscreen" => Ok(BareKey::PrintScreen),
276            "pause" => Ok(BareKey::Pause),
277            "menu" => Ok(BareKey::Menu),
278            "space" => Ok(BareKey::Char(' ')),
279            _ => {
280                if key_str.chars().count() == 1 {
281                    if let Some(character) = key_str.chars().next() {
282                        return Ok(BareKey::Char(character));
283                    }
284                }
285                Err("unsupported key".into())
286            },
287        }
288    }
289}
290
291#[derive(
292    Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, Display,
293)]
294pub enum KeyModifier {
295    Ctrl,
296    Alt,
297    Shift,
298    Super,
299}
300
301impl FromStr for KeyModifier {
302    type Err = Box<dyn std::error::Error>;
303    fn from_str(key_str: &str) -> Result<Self, Self::Err> {
304        match key_str.to_ascii_lowercase().as_str() {
305            "shift" => Ok(KeyModifier::Shift),
306            "alt" => Ok(KeyModifier::Alt),
307            "ctrl" => Ok(KeyModifier::Ctrl),
308            "super" => Ok(KeyModifier::Super),
309            _ => Err("unsupported modifier".into()),
310        }
311    }
312}
313
314impl BareKey {
315    pub fn from_bytes_with_u(bytes: &[u8]) -> Option<Self> {
316        match str::from_utf8(bytes) {
317            Ok("27") => Some(BareKey::Esc),
318            Ok("13") => Some(BareKey::Enter),
319            Ok("9") => Some(BareKey::Tab),
320            Ok("127") => Some(BareKey::Backspace),
321            Ok("57358") => Some(BareKey::CapsLock),
322            Ok("57359") => Some(BareKey::ScrollLock),
323            Ok("57360") => Some(BareKey::NumLock),
324            Ok("57361") => Some(BareKey::PrintScreen),
325            Ok("57362") => Some(BareKey::Pause),
326            Ok("57363") => Some(BareKey::Menu),
327            Ok("57399") => Some(BareKey::Char('0')),
328            Ok("57400") => Some(BareKey::Char('1')),
329            Ok("57401") => Some(BareKey::Char('2')),
330            Ok("57402") => Some(BareKey::Char('3')),
331            Ok("57403") => Some(BareKey::Char('4')),
332            Ok("57404") => Some(BareKey::Char('5')),
333            Ok("57405") => Some(BareKey::Char('6')),
334            Ok("57406") => Some(BareKey::Char('7')),
335            Ok("57407") => Some(BareKey::Char('8')),
336            Ok("57408") => Some(BareKey::Char('9')),
337            Ok("57409") => Some(BareKey::Char('.')),
338            Ok("57410") => Some(BareKey::Char('/')),
339            Ok("57411") => Some(BareKey::Char('*')),
340            Ok("57412") => Some(BareKey::Char('-')),
341            Ok("57413") => Some(BareKey::Char('+')),
342            Ok("57414") => Some(BareKey::Enter),
343            Ok("57415") => Some(BareKey::Char('=')),
344            Ok("57417") => Some(BareKey::Left),
345            Ok("57418") => Some(BareKey::Right),
346            Ok("57419") => Some(BareKey::Up),
347            Ok("57420") => Some(BareKey::Down),
348            Ok("57421") => Some(BareKey::PageUp),
349            Ok("57422") => Some(BareKey::PageDown),
350            Ok("57423") => Some(BareKey::Home),
351            Ok("57424") => Some(BareKey::End),
352            Ok("57425") => Some(BareKey::Insert),
353            Ok("57426") => Some(BareKey::Delete),
354            Ok(num) => u8::from_str_radix(num, 10)
355                .ok()
356                .map(|n| BareKey::Char((n as char).to_ascii_lowercase())),
357            _ => None,
358        }
359    }
360    pub fn from_bytes_with_tilde(bytes: &[u8]) -> Option<Self> {
361        match str::from_utf8(bytes) {
362            Ok("2") => Some(BareKey::Insert),
363            Ok("3") => Some(BareKey::Delete),
364            Ok("5") => Some(BareKey::PageUp),
365            Ok("6") => Some(BareKey::PageDown),
366            Ok("7") => Some(BareKey::Home),
367            Ok("8") => Some(BareKey::End),
368            Ok("11") => Some(BareKey::F(1)),
369            Ok("12") => Some(BareKey::F(2)),
370            Ok("13") => Some(BareKey::F(3)),
371            Ok("14") => Some(BareKey::F(4)),
372            Ok("15") => Some(BareKey::F(5)),
373            Ok("17") => Some(BareKey::F(6)),
374            Ok("18") => Some(BareKey::F(7)),
375            Ok("19") => Some(BareKey::F(8)),
376            Ok("20") => Some(BareKey::F(9)),
377            Ok("21") => Some(BareKey::F(10)),
378            Ok("23") => Some(BareKey::F(11)),
379            Ok("24") => Some(BareKey::F(12)),
380            _ => None,
381        }
382    }
383    pub fn from_bytes_with_no_ending_byte(bytes: &[u8]) -> Option<Self> {
384        match str::from_utf8(bytes) {
385            Ok("1D") | Ok("D") => Some(BareKey::Left),
386            Ok("1C") | Ok("C") => Some(BareKey::Right),
387            Ok("1A") | Ok("A") => Some(BareKey::Up),
388            Ok("1B") | Ok("B") => Some(BareKey::Down),
389            Ok("1H") | Ok("H") => Some(BareKey::Home),
390            Ok("1F") | Ok("F") => Some(BareKey::End),
391            Ok("1P") | Ok("P") => Some(BareKey::F(1)),
392            Ok("1Q") | Ok("Q") => Some(BareKey::F(2)),
393            Ok("1S") | Ok("S") => Some(BareKey::F(4)),
394            _ => None,
395        }
396    }
397}
398
399bitflags::bitflags! {
400    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
401    struct ModifierFlags: u8 {
402        const SHIFT   = 0b0000_0001;
403        const ALT     = 0b0000_0010;
404        const CONTROL = 0b0000_0100;
405        const SUPER   = 0b0000_1000;
406        // we don't actually use the below, left here for completeness in case we want to add them
407        // later
408        const HYPER = 0b0001_0000;
409        const META = 0b0010_0000;
410        const CAPS_LOCK = 0b0100_0000;
411        const NUM_LOCK = 0b1000_0000;
412    }
413}
414
415impl KeyModifier {
416    pub fn from_bytes(bytes: &[u8]) -> BTreeSet<KeyModifier> {
417        let modifier_flags = str::from_utf8(bytes)
418            .ok() // convert to string: (eg. "16")
419            .and_then(|s| u8::from_str_radix(&s, 10).ok()) // convert to u8: (eg. 16)
420            .map(|s| s.saturating_sub(1)) // subtract 1: (eg. 15)
421            .and_then(|b| ModifierFlags::from_bits(b)); // bitflags: (0b0000_1111: Shift, Alt, Control, Super)
422        let mut key_modifiers = BTreeSet::new();
423        if let Some(modifier_flags) = modifier_flags {
424            for name in modifier_flags.iter() {
425                match name {
426                    ModifierFlags::SHIFT => key_modifiers.insert(KeyModifier::Shift),
427                    ModifierFlags::ALT => key_modifiers.insert(KeyModifier::Alt),
428                    ModifierFlags::CONTROL => key_modifiers.insert(KeyModifier::Ctrl),
429                    ModifierFlags::SUPER => key_modifiers.insert(KeyModifier::Super),
430                    _ => false,
431                };
432            }
433        }
434        key_modifiers
435    }
436}
437
438impl KeyWithModifier {
439    pub fn new(bare_key: BareKey) -> Self {
440        KeyWithModifier {
441            bare_key,
442            key_modifiers: BTreeSet::new(),
443        }
444    }
445    pub fn new_with_modifiers(bare_key: BareKey, key_modifiers: BTreeSet<KeyModifier>) -> Self {
446        KeyWithModifier {
447            bare_key,
448            key_modifiers,
449        }
450    }
451    pub fn with_shift_modifier(mut self) -> Self {
452        self.key_modifiers.insert(KeyModifier::Shift);
453        self
454    }
455    pub fn with_alt_modifier(mut self) -> Self {
456        self.key_modifiers.insert(KeyModifier::Alt);
457        self
458    }
459    pub fn with_ctrl_modifier(mut self) -> Self {
460        self.key_modifiers.insert(KeyModifier::Ctrl);
461        self
462    }
463    pub fn with_super_modifier(mut self) -> Self {
464        self.key_modifiers.insert(KeyModifier::Super);
465        self
466    }
467    pub fn from_bytes_with_u(number_bytes: &[u8], modifier_bytes: &[u8]) -> Option<Self> {
468        // CSI number ; modifiers u
469        let bare_key = BareKey::from_bytes_with_u(number_bytes);
470        match bare_key {
471            Some(bare_key) => {
472                let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
473                Some(KeyWithModifier {
474                    bare_key,
475                    key_modifiers,
476                })
477            },
478            _ => None,
479        }
480    }
481    pub fn from_bytes_with_tilde(number_bytes: &[u8], modifier_bytes: &[u8]) -> Option<Self> {
482        // CSI number ; modifiers ~
483        let bare_key = BareKey::from_bytes_with_tilde(number_bytes);
484        match bare_key {
485            Some(bare_key) => {
486                let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
487                Some(KeyWithModifier {
488                    bare_key,
489                    key_modifiers,
490                })
491            },
492            _ => None,
493        }
494    }
495    pub fn from_bytes_with_no_ending_byte(
496        number_bytes: &[u8],
497        modifier_bytes: &[u8],
498    ) -> Option<Self> {
499        // CSI 1; modifiers [ABCDEFHPQS]
500        let bare_key = BareKey::from_bytes_with_no_ending_byte(number_bytes);
501        match bare_key {
502            Some(bare_key) => {
503                let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
504                Some(KeyWithModifier {
505                    bare_key,
506                    key_modifiers,
507                })
508            },
509            _ => None,
510        }
511    }
512    pub fn strip_common_modifiers(&self, common_modifiers: &Vec<KeyModifier>) -> Self {
513        let common_modifiers: BTreeSet<&KeyModifier> = common_modifiers.into_iter().collect();
514        KeyWithModifier {
515            bare_key: self.bare_key.clone(),
516            key_modifiers: self
517                .key_modifiers
518                .iter()
519                .filter(|m| !common_modifiers.contains(m))
520                .cloned()
521                .collect(),
522        }
523    }
524    pub fn is_key_without_modifier(&self, key: BareKey) -> bool {
525        self.bare_key == key && self.key_modifiers.is_empty()
526    }
527    pub fn is_key_with_ctrl_modifier(&self, key: BareKey) -> bool {
528        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Ctrl)
529    }
530    pub fn is_key_with_alt_modifier(&self, key: BareKey) -> bool {
531        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Alt)
532    }
533    pub fn is_key_with_shift_modifier(&self, key: BareKey) -> bool {
534        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Shift)
535    }
536    pub fn is_key_with_super_modifier(&self, key: BareKey) -> bool {
537        self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Super)
538    }
539    pub fn is_cancel_key(&self) -> bool {
540        // self.bare_key == BareKey::Esc || self.is_key_with_ctrl_modifier(BareKey::Char('c'))
541        self.bare_key == BareKey::Esc
542    }
543    #[cfg(not(target_family = "wasm"))]
544    pub fn to_termwiz_modifiers(&self) -> Modifiers {
545        let mut modifiers = Modifiers::empty();
546        for modifier in &self.key_modifiers {
547            modifiers.set(modifier.into(), true);
548        }
549        modifiers
550    }
551    #[cfg(not(target_family = "wasm"))]
552    pub fn to_termwiz_keycode(&self) -> KeyCode {
553        match self.bare_key {
554            BareKey::PageDown => KeyCode::PageDown,
555            BareKey::PageUp => KeyCode::PageUp,
556            BareKey::Left => KeyCode::LeftArrow,
557            BareKey::Down => KeyCode::DownArrow,
558            BareKey::Up => KeyCode::UpArrow,
559            BareKey::Right => KeyCode::RightArrow,
560            BareKey::Home => KeyCode::Home,
561            BareKey::End => KeyCode::End,
562            BareKey::Backspace => KeyCode::Backspace,
563            BareKey::Delete => KeyCode::Delete,
564            BareKey::Insert => KeyCode::Insert,
565            BareKey::F(index) => KeyCode::Function(index),
566            BareKey::Char(character) => KeyCode::Char(character),
567            BareKey::Tab => KeyCode::Tab,
568            BareKey::Esc => KeyCode::Escape,
569            BareKey::Enter => KeyCode::Enter,
570            BareKey::CapsLock => KeyCode::CapsLock,
571            BareKey::ScrollLock => KeyCode::ScrollLock,
572            BareKey::NumLock => KeyCode::NumLock,
573            BareKey::PrintScreen => KeyCode::PrintScreen,
574            BareKey::Pause => KeyCode::Pause,
575            BareKey::Menu => KeyCode::Menu,
576        }
577    }
578    #[cfg(not(target_family = "wasm"))]
579    pub fn serialize_non_kitty(&self) -> Option<String> {
580        let modifiers = self.to_termwiz_modifiers();
581        let key_code_encode_modes = KeyCodeEncodeModes {
582            encoding: KeyboardEncoding::Xterm,
583            // all these flags are false because they have been dealt with before this
584            // serialization
585            application_cursor_keys: false,
586            newline_mode: false,
587            modify_other_keys: None,
588        };
589        self.to_termwiz_keycode()
590            .encode(modifiers, key_code_encode_modes, true)
591            .ok()
592    }
593    #[cfg(not(target_family = "wasm"))]
594    pub fn serialize_kitty(&self) -> Option<String> {
595        let modifiers = self.to_termwiz_modifiers();
596        let key_code_encode_modes = KeyCodeEncodeModes {
597            encoding: KeyboardEncoding::Kitty(KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES),
598            // all these flags are false because they have been dealt with before this
599            // serialization
600            application_cursor_keys: false,
601            newline_mode: false,
602            modify_other_keys: None,
603        };
604        self.to_termwiz_keycode()
605            .encode(modifiers, key_code_encode_modes, true)
606            .ok()
607    }
608    pub fn has_no_modifiers(&self) -> bool {
609        self.key_modifiers.is_empty()
610    }
611    pub fn has_modifiers(&self, modifiers: &[KeyModifier]) -> bool {
612        for modifier in modifiers {
613            if !self.key_modifiers.contains(modifier) {
614                return false;
615            }
616        }
617        true
618    }
619    pub fn has_only_modifiers(&self, modifiers: &[KeyModifier]) -> bool {
620        for modifier in modifiers {
621            if !self.key_modifiers.contains(modifier) {
622                return false;
623            }
624        }
625        if self.key_modifiers.len() != modifiers.len() {
626            return false;
627        }
628        true
629    }
630}
631
632#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
633pub enum Direction {
634    Left,
635    Right,
636    Up,
637    Down,
638}
639
640impl Default for Direction {
641    fn default() -> Self {
642        Direction::Left
643    }
644}
645
646impl Direction {
647    pub fn invert(&self) -> Direction {
648        match *self {
649            Direction::Left => Direction::Right,
650            Direction::Down => Direction::Up,
651            Direction::Up => Direction::Down,
652            Direction::Right => Direction::Left,
653        }
654    }
655
656    pub fn is_horizontal(&self) -> bool {
657        matches!(self, Direction::Left | Direction::Right)
658    }
659
660    pub fn is_vertical(&self) -> bool {
661        matches!(self, Direction::Down | Direction::Up)
662    }
663}
664
665impl fmt::Display for Direction {
666    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667        match self {
668            Direction::Left => write!(f, "←"),
669            Direction::Right => write!(f, "→"),
670            Direction::Up => write!(f, "↑"),
671            Direction::Down => write!(f, "↓"),
672        }
673    }
674}
675
676impl FromStr for Direction {
677    type Err = String;
678    fn from_str(s: &str) -> Result<Self, Self::Err> {
679        match s {
680            "Left" | "left" => Ok(Direction::Left),
681            "Right" | "right" => Ok(Direction::Right),
682            "Up" | "up" => Ok(Direction::Up),
683            "Down" | "down" => Ok(Direction::Down),
684            _ => Err(format!(
685                "Failed to parse Direction. Unknown Direction: {}",
686                s
687            )),
688        }
689    }
690}
691
692/// Resize operation to perform.
693#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)]
694pub enum Resize {
695    Increase,
696    Decrease,
697}
698
699impl Default for Resize {
700    fn default() -> Self {
701        Resize::Increase
702    }
703}
704
705impl Resize {
706    pub fn invert(&self) -> Self {
707        match self {
708            Resize::Increase => Resize::Decrease,
709            Resize::Decrease => Resize::Increase,
710        }
711    }
712}
713
714impl fmt::Display for Resize {
715    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
716        match self {
717            Resize::Increase => write!(f, "+"),
718            Resize::Decrease => write!(f, "-"),
719        }
720    }
721}
722
723impl FromStr for Resize {
724    type Err = String;
725    fn from_str(s: &str) -> Result<Self, Self::Err> {
726        match s {
727            "Increase" | "increase" | "+" => Ok(Resize::Increase),
728            "Decrease" | "decrease" | "-" => Ok(Resize::Decrease),
729            _ => Err(format!(
730                "failed to parse resize type. Unknown specifier '{}'",
731                s
732            )),
733        }
734    }
735}
736
737/// Container type that fully describes resize operations.
738///
739/// This is best thought of as follows:
740///
741/// - `resize` commands how the total *area* of the pane will change as part of this resize
742///   operation.
743/// - `direction` has two meanings:
744///     - `None` means to resize all borders equally
745///     - Anything else means to move the named border to achieve the change in area
746#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)]
747pub struct ResizeStrategy {
748    /// Whether to increase or resize total area
749    pub resize: Resize,
750    /// With which border, if any, to change area
751    pub direction: Option<Direction>,
752    /// If set to true (default), increasing resizes towards a viewport border will be inverted.
753    /// I.e. a scenario like this ("increase right"):
754    ///
755    /// ```text
756    /// +---+---+
757    /// |   | X |->
758    /// +---+---+
759    /// ```
760    ///
761    /// turns into this ("decrease left"):
762    ///
763    /// ```text
764    /// +---+---+
765    /// |   |-> |
766    /// +---+---+
767    /// ```
768    pub invert_on_boundaries: bool,
769}
770
771impl From<Direction> for ResizeStrategy {
772    fn from(direction: Direction) -> Self {
773        ResizeStrategy::new(Resize::Increase, Some(direction))
774    }
775}
776
777impl From<Resize> for ResizeStrategy {
778    fn from(resize: Resize) -> Self {
779        ResizeStrategy::new(resize, None)
780    }
781}
782
783impl ResizeStrategy {
784    pub fn new(resize: Resize, direction: Option<Direction>) -> Self {
785        ResizeStrategy {
786            resize,
787            direction,
788            invert_on_boundaries: true,
789        }
790    }
791
792    pub fn invert(&self) -> ResizeStrategy {
793        let resize = match self.resize {
794            Resize::Increase => Resize::Decrease,
795            Resize::Decrease => Resize::Increase,
796        };
797        let direction = match self.direction {
798            Some(direction) => Some(direction.invert()),
799            None => None,
800        };
801
802        ResizeStrategy::new(resize, direction)
803    }
804
805    pub fn resize_type(&self) -> Resize {
806        self.resize
807    }
808
809    pub fn direction(&self) -> Option<Direction> {
810        self.direction
811    }
812
813    pub fn direction_horizontal(&self) -> bool {
814        matches!(
815            self.direction,
816            Some(Direction::Left) | Some(Direction::Right)
817        )
818    }
819
820    pub fn direction_vertical(&self) -> bool {
821        matches!(self.direction, Some(Direction::Up) | Some(Direction::Down))
822    }
823
824    pub fn resize_increase(&self) -> bool {
825        self.resize == Resize::Increase
826    }
827
828    pub fn resize_decrease(&self) -> bool {
829        self.resize == Resize::Decrease
830    }
831
832    pub fn move_left_border_left(&self) -> bool {
833        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Left))
834    }
835
836    pub fn move_left_border_right(&self) -> bool {
837        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Left))
838    }
839
840    pub fn move_lower_border_down(&self) -> bool {
841        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Down))
842    }
843
844    pub fn move_lower_border_up(&self) -> bool {
845        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Down))
846    }
847
848    pub fn move_upper_border_up(&self) -> bool {
849        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Up))
850    }
851
852    pub fn move_upper_border_down(&self) -> bool {
853        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Up))
854    }
855
856    pub fn move_right_border_right(&self) -> bool {
857        (self.resize == Resize::Increase) && (self.direction == Some(Direction::Right))
858    }
859
860    pub fn move_right_border_left(&self) -> bool {
861        (self.resize == Resize::Decrease) && (self.direction == Some(Direction::Right))
862    }
863
864    pub fn move_all_borders_out(&self) -> bool {
865        (self.resize == Resize::Increase) && (self.direction == None)
866    }
867
868    pub fn move_all_borders_in(&self) -> bool {
869        (self.resize == Resize::Decrease) && (self.direction == None)
870    }
871}
872
873impl fmt::Display for ResizeStrategy {
874    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
875        let resize = match self.resize {
876            Resize::Increase => "increase",
877            Resize::Decrease => "decrease",
878        };
879        let border = match self.direction {
880            Some(Direction::Left) => "left",
881            Some(Direction::Down) => "bottom",
882            Some(Direction::Up) => "top",
883            Some(Direction::Right) => "right",
884            None => "every",
885        };
886
887        write!(f, "{} size on {} border", resize, border)
888    }
889}
890
891#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
892// FIXME: This should be extended to handle different button clicks (not just
893// left click) and the `ScrollUp` and `ScrollDown` events could probably be
894// merged into a single `Scroll(isize)` event.
895pub enum Mouse {
896    ScrollUp(usize),          // number of lines
897    ScrollDown(usize),        // number of lines
898    LeftClick(isize, usize),  // line and column
899    RightClick(isize, usize), // line and column
900    Hold(isize, usize),       // line and column
901    Release(isize, usize),    // line and column
902    Hover(isize, usize),      // line and column
903}
904
905impl Mouse {
906    pub fn position(&self) -> Option<(usize, usize)> {
907        // (line, column)
908        match self {
909            Mouse::LeftClick(line, column) => Some((*line as usize, *column as usize)),
910            Mouse::RightClick(line, column) => Some((*line as usize, *column as usize)),
911            Mouse::Hold(line, column) => Some((*line as usize, *column as usize)),
912            Mouse::Release(line, column) => Some((*line as usize, *column as usize)),
913            Mouse::Hover(line, column) => Some((*line as usize, *column as usize)),
914            _ => None,
915        }
916    }
917}
918
919#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
920pub struct FileMetadata {
921    pub is_dir: bool,
922    pub is_file: bool,
923    pub is_symlink: bool,
924    pub len: u64,
925}
926
927impl From<Metadata> for FileMetadata {
928    fn from(metadata: Metadata) -> Self {
929        FileMetadata {
930            is_dir: metadata.is_dir(),
931            is_file: metadata.is_file(),
932            is_symlink: metadata.is_symlink(),
933            len: metadata.len(),
934        }
935    }
936}
937
938/// These events can be subscribed to with subscribe method exported by `zellij-tile`.
939/// Once subscribed to, they will trigger the `update` method of the `ZellijPlugin` trait.
940#[derive(Debug, Clone, PartialEq, EnumDiscriminants, Display, Serialize, Deserialize)]
941#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
942#[strum_discriminants(name(EventType))]
943#[non_exhaustive]
944pub enum Event {
945    ModeUpdate(ModeInfo),
946    TabUpdate(Vec<TabInfo>),
947    PaneUpdate(PaneManifest),
948    /// A key was pressed while the user is focused on this plugin's pane
949    Key(KeyWithModifier),
950    /// A mouse event happened while the user is focused on this plugin's pane
951    Mouse(Mouse),
952    /// A timer expired set by the `set_timeout` method exported by `zellij-tile`.
953    Timer(f64),
954    /// Text was copied to the clipboard anywhere in the app
955    CopyToClipboard(CopyDestination),
956    /// Failed to copy text to clipboard anywhere in the app
957    SystemClipboardFailure,
958    /// Input was received anywhere in the app
959    InputReceived,
960    /// This plugin became visible or invisible
961    Visible(bool),
962    /// A message from one of the plugin's workers
963    CustomMessage(
964        String, // message
965        String, // payload
966    ),
967    /// A file was created somewhere in the Zellij CWD folder
968    FileSystemCreate(Vec<(PathBuf, Option<FileMetadata>)>),
969    /// A file was accessed somewhere in the Zellij CWD folder
970    FileSystemRead(Vec<(PathBuf, Option<FileMetadata>)>),
971    /// A file was modified somewhere in the Zellij CWD folder
972    FileSystemUpdate(Vec<(PathBuf, Option<FileMetadata>)>),
973    /// A file was deleted somewhere in the Zellij CWD folder
974    FileSystemDelete(Vec<(PathBuf, Option<FileMetadata>)>),
975    /// A Result of plugin permission request
976    PermissionRequestResult(PermissionStatus),
977    SessionUpdate(
978        Vec<SessionInfo>,
979        Vec<(String, Duration)>, // resurrectable sessions
980    ),
981    RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR,
982    // context
983    WebRequestResult(
984        u16,
985        BTreeMap<String, String>,
986        Vec<u8>,
987        BTreeMap<String, String>,
988    ), // status,
989    // headers,
990    // body,
991    // context
992    CommandPaneOpened(u32, Context), // u32 - terminal_pane_id
993    CommandPaneExited(u32, Option<i32>, Context), // u32 - terminal_pane_id, Option<i32> -
994    // exit_code
995    PaneClosed(PaneId),
996    EditPaneOpened(u32, Context),              // u32 - terminal_pane_id
997    EditPaneExited(u32, Option<i32>, Context), // u32 - terminal_pane_id, Option<i32> - exit code
998    CommandPaneReRun(u32, Context),            // u32 - terminal_pane_id, Option<i32> -
999    FailedToWriteConfigToDisk(Option<String>), // String -> the file path we failed to write
1000    ListClients(Vec<ClientInfo>),
1001    HostFolderChanged(PathBuf),               // PathBuf -> new host folder
1002    FailedToChangeHostFolder(Option<String>), // String -> the error we got when changing
1003    PastedText(String),
1004    ConfigWasWrittenToDisk,
1005    WebServerStatus(WebServerStatus),
1006    FailedToStartWebServer(String),
1007    BeforeClose,
1008    InterceptedKeyPress(KeyWithModifier),
1009    /// An action was performed by the user (requires InterceptInput permission)
1010    UserAction(Action, ClientId, Option<u32>, Option<ClientId>), // Action, client_id, terminal_id, cli_client_id
1011    PaneRenderReport(HashMap<PaneId, PaneContents>),
1012    PaneRenderReportWithAnsi(HashMap<PaneId, PaneContents>),
1013    ActionComplete(Action, Option<PaneId>, BTreeMap<String, String>), // Action, pane_id, context
1014    CwdChanged(PaneId, PathBuf, Vec<ClientId>), // pane_id, cwd, focused_client_ids
1015    AvailableLayoutInfo(Vec<LayoutInfo>, Vec<LayoutWithError>),
1016    PluginConfigurationChanged(BTreeMap<String, String>),
1017    HighlightClicked {
1018        pane_id: PaneId,
1019        pattern: String,
1020        matched_string: String,
1021        context: BTreeMap<String, String>,
1022    },
1023    /// Initial keybindings sent once on plugin load and on reconfiguration.
1024    /// Plugins that subscribe to this event signal they cache keybindings
1025    /// and can handle lightweight ModeUpdate events without keybindings.
1026    InitialKeybinds(KeybindsVec),
1027}
1028
1029#[derive(Debug, Clone, PartialEq, Eq, EnumDiscriminants, Display, Serialize, Deserialize)]
1030pub enum WebServerStatus {
1031    Online(String), // String -> base url
1032    Offline,
1033    DifferentVersion(String), // version
1034}
1035
1036#[derive(
1037    Debug,
1038    PartialEq,
1039    Eq,
1040    Hash,
1041    Copy,
1042    Clone,
1043    EnumDiscriminants,
1044    Display,
1045    Serialize,
1046    Deserialize,
1047    PartialOrd,
1048    Ord,
1049)]
1050#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize, Display, PartialOrd, Ord))]
1051#[strum_discriminants(name(PermissionType))]
1052#[non_exhaustive]
1053pub enum Permission {
1054    ReadApplicationState,
1055    ChangeApplicationState,
1056    OpenFiles,
1057    RunCommands,
1058    OpenTerminalsOrPlugins,
1059    WriteToStdin,
1060    WebAccess,
1061    ReadCliPipes,
1062    MessageAndLaunchOtherPlugins,
1063    Reconfigure,
1064    FullHdAccess,
1065    StartWebServer,
1066    InterceptInput,
1067    ReadPaneContents,
1068    RunActionsAsUser,
1069    WriteToClipboard,
1070    ReadSessionEnvironmentVariables,
1071}
1072
1073impl PermissionType {
1074    pub fn display_name(&self) -> String {
1075        match self {
1076            PermissionType::ReadApplicationState => {
1077                "Access Zellij state (Panes, Tabs and UI)".to_owned()
1078            },
1079            PermissionType::ChangeApplicationState => {
1080                "Change Zellij state (Panes, Tabs and UI) and run commands".to_owned()
1081            },
1082            PermissionType::OpenFiles => "Open files (eg. for editing)".to_owned(),
1083            PermissionType::RunCommands => "Run commands".to_owned(),
1084            PermissionType::OpenTerminalsOrPlugins => "Start new terminals and plugins".to_owned(),
1085            PermissionType::WriteToStdin => "Write to standard input (STDIN)".to_owned(),
1086            PermissionType::WebAccess => "Make web requests".to_owned(),
1087            PermissionType::ReadCliPipes => "Control command line pipes and output".to_owned(),
1088            PermissionType::MessageAndLaunchOtherPlugins => {
1089                "Send messages to and launch other plugins".to_owned()
1090            },
1091            PermissionType::Reconfigure => "Change Zellij runtime configuration".to_owned(),
1092            PermissionType::FullHdAccess => "Full access to the hard-drive".to_owned(),
1093            PermissionType::StartWebServer => {
1094                "Start a local web server to serve Zellij sessions".to_owned()
1095            },
1096            PermissionType::InterceptInput => "Intercept Input (keyboard & mouse)".to_owned(),
1097            PermissionType::ReadPaneContents => {
1098                "Read pane contents (viewport and selection)".to_owned()
1099            },
1100            PermissionType::RunActionsAsUser => "Execute actions as the user".to_owned(),
1101            PermissionType::WriteToClipboard => "Write to clipboard".to_owned(),
1102            PermissionType::ReadSessionEnvironmentVariables => {
1103                "Read environment variables present upon session creation".to_owned()
1104            },
1105        }
1106    }
1107}
1108
1109#[derive(Debug, Clone)]
1110pub struct PluginPermission {
1111    pub name: String,
1112    pub permissions: Vec<PermissionType>,
1113}
1114
1115impl PluginPermission {
1116    pub fn new(name: String, permissions: Vec<PermissionType>) -> Self {
1117        PluginPermission { name, permissions }
1118    }
1119}
1120
1121/// Describes the different input modes, which change the way that keystrokes will be interpreted.
1122#[derive(
1123    Debug,
1124    PartialEq,
1125    Eq,
1126    Hash,
1127    Copy,
1128    Clone,
1129    EnumIter,
1130    Serialize,
1131    Deserialize,
1132    ArgEnum,
1133    PartialOrd,
1134    Ord,
1135)]
1136pub enum InputMode {
1137    /// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading
1138    /// to other modes
1139    #[serde(alias = "normal")]
1140    Normal,
1141    /// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled
1142    /// except the one leading back to normal mode
1143    #[serde(alias = "locked")]
1144    Locked,
1145    /// `Resize` mode allows resizing the different existing panes.
1146    #[serde(alias = "resize")]
1147    Resize,
1148    /// `Pane` mode allows creating and closing panes, as well as moving between them.
1149    #[serde(alias = "pane")]
1150    Pane,
1151    /// `Tab` mode allows creating and closing tabs, as well as moving between them.
1152    #[serde(alias = "tab")]
1153    Tab,
1154    /// `Scroll` mode allows scrolling up and down within a pane.
1155    #[serde(alias = "scroll")]
1156    Scroll,
1157    /// `EnterSearch` mode allows for typing in the needle for a search in the scroll buffer of a pane.
1158    #[serde(alias = "entersearch")]
1159    EnterSearch,
1160    /// `Search` mode allows for searching a term in a pane (superset of `Scroll`).
1161    #[serde(alias = "search")]
1162    Search,
1163    /// `RenameTab` mode allows assigning a new name to a tab.
1164    #[serde(alias = "renametab")]
1165    RenameTab,
1166    /// `RenamePane` mode allows assigning a new name to a pane.
1167    #[serde(alias = "renamepane")]
1168    RenamePane,
1169    /// `Session` mode allows detaching sessions
1170    #[serde(alias = "session")]
1171    Session,
1172    /// `Move` mode allows moving the different existing panes within a tab
1173    #[serde(alias = "move")]
1174    Move,
1175    /// `Prompt` mode allows interacting with active prompts.
1176    #[serde(alias = "prompt")]
1177    Prompt,
1178    /// `Tmux` mode allows for basic tmux keybindings functionality
1179    #[serde(alias = "tmux")]
1180    Tmux,
1181}
1182
1183impl Default for InputMode {
1184    fn default() -> InputMode {
1185        InputMode::Normal
1186    }
1187}
1188
1189#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
1190pub enum ThemeHue {
1191    Light,
1192    Dark,
1193}
1194impl Default for ThemeHue {
1195    fn default() -> ThemeHue {
1196        ThemeHue::Dark
1197    }
1198}
1199
1200#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
1201pub enum PaletteColor {
1202    Rgb((u8, u8, u8)),
1203    EightBit(u8),
1204}
1205impl Default for PaletteColor {
1206    fn default() -> PaletteColor {
1207        PaletteColor::EightBit(0)
1208    }
1209}
1210
1211/// Priority layer for plugin-supplied regex highlights.
1212/// Higher-priority layers take visual precedence over lower ones
1213/// when highlights overlap.  Built-in highlights (mouse selection,
1214/// search results) always take precedence over all plugin layers.
1215#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
1216pub enum HighlightLayer {
1217    Hint,           // lowest: pure pattern matching (paths, URLs, IPs)
1218    Tool,           // middle: backed by runtime domain knowledge (git, docker, k8s)
1219    ActionFeedback, // highest: result of an explicit user action (search, bookmarks)
1220}
1221
1222impl Default for HighlightLayer {
1223    fn default() -> Self {
1224        HighlightLayer::Hint
1225    }
1226}
1227
1228/// Style for a plugin-supplied regex highlight.
1229/// Theme-based variants reference `style.colors.text_unselected.*`.
1230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1231pub enum HighlightStyle {
1232    None,      // no color override — use with bold/italic/underline for style-only highlights
1233    Emphasis0, // fg = emphasis_0, no bg override
1234    Emphasis1, // fg = emphasis_1, no bg override
1235    Emphasis2, // fg = emphasis_2, no bg override
1236    Emphasis3, // fg = emphasis_3, no bg override
1237    BackgroundEmphasis0, // bg = emphasis_0, fg = background
1238    BackgroundEmphasis1, // bg = emphasis_1, fg = background
1239    BackgroundEmphasis2, // bg = emphasis_2, fg = background
1240    BackgroundEmphasis3, // bg = emphasis_3, fg = background
1241    CustomRgb {
1242        fg: Option<(u8, u8, u8)>,
1243        bg: Option<(u8, u8, u8)>,
1244    },
1245    CustomIndex {
1246        fg: Option<u8>,
1247        bg: Option<u8>,
1248    },
1249}
1250
1251/// One pattern + style pair sent by a plugin.
1252#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1253pub struct RegexHighlight {
1254    pub pattern: String, // key for upsert; also the regex source
1255    pub style: HighlightStyle,
1256    pub layer: HighlightLayer,
1257    pub context: BTreeMap<String, String>, // arbitrary data echoed back verbatim on click
1258    pub on_hover: bool, // if true, only rendered when the cursor overlaps this match
1259    pub bold: bool,
1260    pub italic: bool,
1261    pub underline: bool,
1262    pub tooltip_text: Option<String>, // shown at bottom of pane frame when hovering over match
1263}
1264
1265// these are used for the web client
1266impl PaletteColor {
1267    pub fn as_rgb_str(&self) -> String {
1268        let (r, g, b) = match *self {
1269            Self::Rgb((r, g, b)) => (r, g, b),
1270            Self::EightBit(c) => eightbit_to_rgb(c),
1271        };
1272        format!("rgb({}, {}, {})", r, g, b)
1273    }
1274    pub fn from_rgb_str(rgb_str: &str) -> Self {
1275        let trimmed = rgb_str.trim();
1276
1277        if !trimmed.starts_with("rgb(") || !trimmed.ends_with(')') {
1278            return Self::default();
1279        }
1280
1281        let inner = trimmed
1282            .strip_prefix("rgb(")
1283            .and_then(|s| s.strip_suffix(')'))
1284            .unwrap_or("");
1285
1286        let parts: Vec<&str> = inner.split(',').collect();
1287
1288        if parts.len() != 3 {
1289            return Self::default();
1290        }
1291
1292        let mut rgb_values = [0u8; 3];
1293        for (i, part) in parts.iter().enumerate() {
1294            if let Some(rgb_val) = rgb_values.get_mut(i) {
1295                if let Ok(parsed) = part.trim().parse::<u8>() {
1296                    *rgb_val = parsed;
1297                } else {
1298                    return Self::default();
1299                }
1300            }
1301        }
1302
1303        Self::Rgb((rgb_values[0], rgb_values[1], rgb_values[2]))
1304    }
1305}
1306
1307impl FromStr for InputMode {
1308    type Err = ConversionError;
1309
1310    fn from_str(s: &str) -> Result<Self, ConversionError> {
1311        match s {
1312            "normal" | "Normal" => Ok(InputMode::Normal),
1313            "locked" | "Locked" => Ok(InputMode::Locked),
1314            "resize" | "Resize" => Ok(InputMode::Resize),
1315            "pane" | "Pane" => Ok(InputMode::Pane),
1316            "tab" | "Tab" => Ok(InputMode::Tab),
1317            "search" | "Search" => Ok(InputMode::Search),
1318            "scroll" | "Scroll" => Ok(InputMode::Scroll),
1319            "renametab" | "RenameTab" => Ok(InputMode::RenameTab),
1320            "renamepane" | "RenamePane" => Ok(InputMode::RenamePane),
1321            "session" | "Session" => Ok(InputMode::Session),
1322            "move" | "Move" => Ok(InputMode::Move),
1323            "prompt" | "Prompt" => Ok(InputMode::Prompt),
1324            "tmux" | "Tmux" => Ok(InputMode::Tmux),
1325            "entersearch" | "Entersearch" | "EnterSearch" => Ok(InputMode::EnterSearch),
1326            e => Err(ConversionError::UnknownInputMode(e.into())),
1327        }
1328    }
1329}
1330
1331#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
1332pub enum PaletteSource {
1333    Default,
1334    Xresources,
1335}
1336impl Default for PaletteSource {
1337    fn default() -> PaletteSource {
1338        PaletteSource::Default
1339    }
1340}
1341#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
1342pub struct Palette {
1343    pub source: PaletteSource,
1344    pub theme_hue: ThemeHue,
1345    pub fg: PaletteColor,
1346    pub bg: PaletteColor,
1347    pub black: PaletteColor,
1348    pub red: PaletteColor,
1349    pub green: PaletteColor,
1350    pub yellow: PaletteColor,
1351    pub blue: PaletteColor,
1352    pub magenta: PaletteColor,
1353    pub cyan: PaletteColor,
1354    pub white: PaletteColor,
1355    pub orange: PaletteColor,
1356    pub gray: PaletteColor,
1357    pub purple: PaletteColor,
1358    pub gold: PaletteColor,
1359    pub silver: PaletteColor,
1360    pub pink: PaletteColor,
1361    pub brown: PaletteColor,
1362}
1363
1364#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
1365pub struct Style {
1366    pub colors: Styling,
1367    pub rounded_corners: bool,
1368    pub hide_session_name: bool,
1369}
1370
1371#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1372pub enum Coloration {
1373    NoStyling,
1374    Styled(StyleDeclaration),
1375}
1376
1377impl Coloration {
1378    pub fn with_fallback(&self, fallback: StyleDeclaration) -> StyleDeclaration {
1379        match &self {
1380            Coloration::NoStyling => fallback,
1381            Coloration::Styled(style) => *style,
1382        }
1383    }
1384}
1385
1386#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1387pub struct Styling {
1388    pub text_unselected: StyleDeclaration,
1389    pub text_selected: StyleDeclaration,
1390    pub ribbon_unselected: StyleDeclaration,
1391    pub ribbon_selected: StyleDeclaration,
1392    pub table_title: StyleDeclaration,
1393    pub table_cell_unselected: StyleDeclaration,
1394    pub table_cell_selected: StyleDeclaration,
1395    pub list_unselected: StyleDeclaration,
1396    pub list_selected: StyleDeclaration,
1397    pub frame_unselected: Option<StyleDeclaration>,
1398    pub frame_selected: StyleDeclaration,
1399    pub frame_highlight: StyleDeclaration,
1400    pub exit_code_success: StyleDeclaration,
1401    pub exit_code_error: StyleDeclaration,
1402    pub multiplayer_user_colors: MultiplayerColors,
1403}
1404
1405#[derive(Debug, Copy, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1406pub struct StyleDeclaration {
1407    pub base: PaletteColor,
1408    pub background: PaletteColor,
1409    pub emphasis_0: PaletteColor,
1410    pub emphasis_1: PaletteColor,
1411    pub emphasis_2: PaletteColor,
1412    pub emphasis_3: PaletteColor,
1413}
1414
1415#[derive(Debug, Copy, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
1416pub struct MultiplayerColors {
1417    pub player_1: PaletteColor,
1418    pub player_2: PaletteColor,
1419    pub player_3: PaletteColor,
1420    pub player_4: PaletteColor,
1421    pub player_5: PaletteColor,
1422    pub player_6: PaletteColor,
1423    pub player_7: PaletteColor,
1424    pub player_8: PaletteColor,
1425    pub player_9: PaletteColor,
1426    pub player_10: PaletteColor,
1427}
1428
1429pub const DEFAULT_STYLES: Styling = Styling {
1430    text_unselected: StyleDeclaration {
1431        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1432        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1433        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1434        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1435        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1436        background: PaletteColor::EightBit(default_colors::GRAY),
1437    },
1438    text_selected: StyleDeclaration {
1439        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1440        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1441        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1442        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1443        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1444        background: PaletteColor::EightBit(default_colors::GRAY),
1445    },
1446    ribbon_unselected: StyleDeclaration {
1447        base: PaletteColor::EightBit(default_colors::BLACK),
1448        emphasis_0: PaletteColor::EightBit(default_colors::RED),
1449        emphasis_1: PaletteColor::EightBit(default_colors::WHITE),
1450        emphasis_2: PaletteColor::EightBit(default_colors::BLUE),
1451        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1452        background: PaletteColor::EightBit(default_colors::GRAY),
1453    },
1454    ribbon_selected: StyleDeclaration {
1455        base: PaletteColor::EightBit(default_colors::BLACK),
1456        emphasis_0: PaletteColor::EightBit(default_colors::RED),
1457        emphasis_1: PaletteColor::EightBit(default_colors::ORANGE),
1458        emphasis_2: PaletteColor::EightBit(default_colors::MAGENTA),
1459        emphasis_3: PaletteColor::EightBit(default_colors::BLUE),
1460        background: PaletteColor::EightBit(default_colors::GREEN),
1461    },
1462    exit_code_success: StyleDeclaration {
1463        base: PaletteColor::EightBit(default_colors::GREEN),
1464        emphasis_0: PaletteColor::EightBit(default_colors::CYAN),
1465        emphasis_1: PaletteColor::EightBit(default_colors::BLACK),
1466        emphasis_2: PaletteColor::EightBit(default_colors::MAGENTA),
1467        emphasis_3: PaletteColor::EightBit(default_colors::BLUE),
1468        background: PaletteColor::EightBit(default_colors::GRAY),
1469    },
1470    exit_code_error: StyleDeclaration {
1471        base: PaletteColor::EightBit(default_colors::RED),
1472        emphasis_0: PaletteColor::EightBit(default_colors::YELLOW),
1473        emphasis_1: PaletteColor::EightBit(default_colors::GOLD),
1474        emphasis_2: PaletteColor::EightBit(default_colors::SILVER),
1475        emphasis_3: PaletteColor::EightBit(default_colors::PURPLE),
1476        background: PaletteColor::EightBit(default_colors::GRAY),
1477    },
1478    frame_unselected: None,
1479    frame_selected: StyleDeclaration {
1480        base: PaletteColor::EightBit(default_colors::GREEN),
1481        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1482        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1483        emphasis_2: PaletteColor::EightBit(default_colors::MAGENTA),
1484        emphasis_3: PaletteColor::EightBit(default_colors::BROWN),
1485        background: PaletteColor::EightBit(default_colors::GRAY),
1486    },
1487    frame_highlight: StyleDeclaration {
1488        base: PaletteColor::EightBit(default_colors::ORANGE),
1489        emphasis_0: PaletteColor::EightBit(default_colors::MAGENTA),
1490        emphasis_1: PaletteColor::EightBit(default_colors::PURPLE),
1491        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1492        emphasis_3: PaletteColor::EightBit(default_colors::GREEN),
1493        background: PaletteColor::EightBit(default_colors::GREEN),
1494    },
1495    table_title: StyleDeclaration {
1496        base: PaletteColor::EightBit(default_colors::GREEN),
1497        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1498        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1499        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1500        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1501        background: PaletteColor::EightBit(default_colors::GRAY),
1502    },
1503    table_cell_unselected: StyleDeclaration {
1504        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1505        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1506        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1507        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1508        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1509        background: PaletteColor::EightBit(default_colors::GRAY),
1510    },
1511    table_cell_selected: StyleDeclaration {
1512        base: PaletteColor::EightBit(default_colors::GREEN),
1513        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1514        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1515        emphasis_2: PaletteColor::EightBit(default_colors::RED),
1516        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1517        background: PaletteColor::EightBit(default_colors::GRAY),
1518    },
1519    list_unselected: StyleDeclaration {
1520        base: PaletteColor::EightBit(default_colors::BRIGHT_GRAY),
1521        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1522        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1523        emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
1524        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1525        background: PaletteColor::EightBit(default_colors::GRAY),
1526    },
1527    list_selected: StyleDeclaration {
1528        base: PaletteColor::EightBit(default_colors::GREEN),
1529        emphasis_0: PaletteColor::EightBit(default_colors::ORANGE),
1530        emphasis_1: PaletteColor::EightBit(default_colors::CYAN),
1531        emphasis_2: PaletteColor::EightBit(default_colors::RED),
1532        emphasis_3: PaletteColor::EightBit(default_colors::MAGENTA),
1533        background: PaletteColor::EightBit(default_colors::GRAY),
1534    },
1535    multiplayer_user_colors: MultiplayerColors {
1536        player_1: PaletteColor::EightBit(default_colors::MAGENTA),
1537        player_2: PaletteColor::EightBit(default_colors::BLUE),
1538        player_3: PaletteColor::EightBit(default_colors::PURPLE),
1539        player_4: PaletteColor::EightBit(default_colors::YELLOW),
1540        player_5: PaletteColor::EightBit(default_colors::CYAN),
1541        player_6: PaletteColor::EightBit(default_colors::GOLD),
1542        player_7: PaletteColor::EightBit(default_colors::RED),
1543        player_8: PaletteColor::EightBit(default_colors::SILVER),
1544        player_9: PaletteColor::EightBit(default_colors::PINK),
1545        player_10: PaletteColor::EightBit(default_colors::BROWN),
1546    },
1547};
1548
1549impl Default for Styling {
1550    fn default() -> Self {
1551        DEFAULT_STYLES
1552    }
1553}
1554
1555impl From<Styling> for Palette {
1556    fn from(styling: Styling) -> Self {
1557        Palette {
1558            theme_hue: ThemeHue::Dark,
1559            source: PaletteSource::Default,
1560            fg: styling.ribbon_unselected.background,
1561            bg: styling.text_unselected.background,
1562            red: styling.exit_code_error.base,
1563            green: styling.text_unselected.emphasis_2,
1564            yellow: styling.exit_code_error.emphasis_0,
1565            blue: styling.ribbon_unselected.emphasis_2,
1566            magenta: styling.text_unselected.emphasis_3,
1567            orange: styling.text_unselected.emphasis_0,
1568            cyan: styling.text_unselected.emphasis_1,
1569            black: styling.ribbon_unselected.base,
1570            white: styling.ribbon_unselected.emphasis_1,
1571            gray: styling.list_unselected.background,
1572            purple: styling.multiplayer_user_colors.player_3,
1573            gold: styling.multiplayer_user_colors.player_6,
1574            silver: styling.multiplayer_user_colors.player_8,
1575            pink: styling.multiplayer_user_colors.player_9,
1576            brown: styling.multiplayer_user_colors.player_10,
1577        }
1578    }
1579}
1580
1581impl From<Palette> for Styling {
1582    fn from(palette: Palette) -> Self {
1583        let (fg, bg) = match palette.theme_hue {
1584            ThemeHue::Light => (palette.black, palette.white),
1585            ThemeHue::Dark => (palette.white, palette.black),
1586        };
1587        Styling {
1588            text_unselected: StyleDeclaration {
1589                base: fg,
1590                emphasis_0: palette.orange,
1591                emphasis_1: palette.cyan,
1592                emphasis_2: palette.green,
1593                emphasis_3: palette.magenta,
1594                background: bg,
1595            },
1596            text_selected: StyleDeclaration {
1597                base: fg,
1598                emphasis_0: palette.orange,
1599                emphasis_1: palette.cyan,
1600                emphasis_2: palette.green,
1601                emphasis_3: palette.magenta,
1602                background: palette.bg,
1603            },
1604            ribbon_unselected: StyleDeclaration {
1605                base: palette.black,
1606                emphasis_0: palette.red,
1607                emphasis_1: palette.white,
1608                emphasis_2: palette.blue,
1609                emphasis_3: palette.magenta,
1610                background: palette.fg,
1611            },
1612            ribbon_selected: StyleDeclaration {
1613                base: palette.black,
1614                emphasis_0: palette.red,
1615                emphasis_1: palette.orange,
1616                emphasis_2: palette.magenta,
1617                emphasis_3: palette.blue,
1618                background: palette.green,
1619            },
1620            exit_code_success: StyleDeclaration {
1621                base: palette.green,
1622                emphasis_0: palette.cyan,
1623                emphasis_1: palette.black,
1624                emphasis_2: palette.magenta,
1625                emphasis_3: palette.blue,
1626                background: Default::default(),
1627            },
1628            exit_code_error: StyleDeclaration {
1629                base: palette.red,
1630                emphasis_0: palette.yellow,
1631                emphasis_1: palette.gold,
1632                emphasis_2: palette.silver,
1633                emphasis_3: palette.purple,
1634                background: Default::default(),
1635            },
1636            frame_unselected: None,
1637            frame_selected: StyleDeclaration {
1638                base: palette.green,
1639                emphasis_0: palette.orange,
1640                emphasis_1: palette.cyan,
1641                emphasis_2: palette.magenta,
1642                emphasis_3: palette.brown,
1643                background: Default::default(),
1644            },
1645            frame_highlight: StyleDeclaration {
1646                base: palette.orange,
1647                emphasis_0: palette.magenta,
1648                emphasis_1: palette.purple,
1649                emphasis_2: palette.orange,
1650                emphasis_3: palette.orange,
1651                background: Default::default(),
1652            },
1653            table_title: StyleDeclaration {
1654                base: palette.green,
1655                emphasis_0: palette.orange,
1656                emphasis_1: palette.cyan,
1657                emphasis_2: palette.green,
1658                emphasis_3: palette.magenta,
1659                background: palette.gray,
1660            },
1661            table_cell_unselected: StyleDeclaration {
1662                base: fg,
1663                emphasis_0: palette.orange,
1664                emphasis_1: palette.cyan,
1665                emphasis_2: palette.green,
1666                emphasis_3: palette.magenta,
1667                background: palette.black,
1668            },
1669            table_cell_selected: StyleDeclaration {
1670                base: fg,
1671                emphasis_0: palette.orange,
1672                emphasis_1: palette.cyan,
1673                emphasis_2: palette.green,
1674                emphasis_3: palette.magenta,
1675                background: palette.bg,
1676            },
1677            list_unselected: StyleDeclaration {
1678                base: palette.white,
1679                emphasis_0: palette.orange,
1680                emphasis_1: palette.cyan,
1681                emphasis_2: palette.green,
1682                emphasis_3: palette.magenta,
1683                background: palette.black,
1684            },
1685            list_selected: StyleDeclaration {
1686                base: palette.white,
1687                emphasis_0: palette.orange,
1688                emphasis_1: palette.cyan,
1689                emphasis_2: palette.green,
1690                emphasis_3: palette.magenta,
1691                background: palette.bg,
1692            },
1693            multiplayer_user_colors: MultiplayerColors {
1694                player_1: palette.magenta,
1695                player_2: palette.blue,
1696                player_3: palette.purple,
1697                player_4: palette.yellow,
1698                player_5: palette.cyan,
1699                player_6: palette.gold,
1700                player_7: palette.red,
1701                player_8: palette.silver,
1702                player_9: palette.pink,
1703                player_10: palette.brown,
1704            },
1705        }
1706    }
1707}
1708
1709// FIXME: Poor devs hashtable since HashTable can't derive `Default`...
1710pub type KeybindsVec = Vec<(InputMode, Vec<(KeyWithModifier, Vec<Action>)>)>;
1711
1712/// Provides information helpful in rendering the Zellij controls for UI bars
1713#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1714pub struct ModeInfo {
1715    pub mode: InputMode,
1716    pub base_mode: Option<InputMode>,
1717    pub keybinds: KeybindsVec,
1718    pub style: Style,
1719    pub capabilities: PluginCapabilities,
1720    pub session_name: Option<String>,
1721    pub editor: Option<PathBuf>,
1722    pub shell: Option<PathBuf>,
1723    pub web_clients_allowed: Option<bool>,
1724    pub web_sharing: Option<WebSharing>,
1725    pub currently_marking_pane_group: Option<bool>,
1726    pub is_web_client: Option<bool>,
1727    // note: these are only the configured ip/port that will be bound if and when the server is up
1728    pub web_server_ip: Option<IpAddr>,
1729    pub web_server_port: Option<u16>,
1730    pub web_server_capability: Option<bool>,
1731}
1732
1733impl ModeInfo {
1734    pub fn get_mode_keybinds(&self) -> Vec<(KeyWithModifier, Vec<Action>)> {
1735        self.get_keybinds_for_mode(self.mode)
1736    }
1737
1738    pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(KeyWithModifier, Vec<Action>)> {
1739        for (vec_mode, map) in &self.keybinds {
1740            if mode == *vec_mode {
1741                return map.to_vec();
1742            }
1743        }
1744        vec![]
1745    }
1746    pub fn update_keybinds(&mut self, keybinds: Keybinds) {
1747        self.keybinds = keybinds.to_keybinds_vec();
1748    }
1749    pub fn update_default_mode(&mut self, new_default_mode: InputMode) {
1750        self.base_mode = Some(new_default_mode);
1751    }
1752    pub fn update_theme(&mut self, theme: Styling) {
1753        self.style.colors = theme.into();
1754    }
1755    pub fn update_rounded_corners(&mut self, rounded_corners: bool) {
1756        self.style.rounded_corners = rounded_corners;
1757    }
1758    pub fn update_arrow_fonts(&mut self, should_support_arrow_fonts: bool) {
1759        // it is honestly quite baffling to me how "arrow_fonts: false" can mean "I support arrow
1760        // fonts", but since this is a public API... ¯\_(ツ)_/¯
1761        self.capabilities.arrow_fonts = !should_support_arrow_fonts;
1762    }
1763    pub fn update_hide_session_name(&mut self, hide_session_name: bool) {
1764        self.style.hide_session_name = hide_session_name;
1765    }
1766    pub fn change_to_default_mode(&mut self) {
1767        if let Some(base_mode) = self.base_mode {
1768            self.mode = base_mode;
1769        }
1770    }
1771}
1772
1773#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
1774pub struct SessionInfo {
1775    pub name: String,
1776    pub tabs: Vec<TabInfo>,
1777    pub panes: PaneManifest,
1778    pub connected_clients: usize,
1779    pub is_current_session: bool,
1780    pub available_layouts: Vec<LayoutInfo>,
1781    pub plugins: BTreeMap<u32, PluginInfo>,
1782    pub web_clients_allowed: bool,
1783    pub web_client_count: usize,
1784    pub tab_history: BTreeMap<ClientId, Vec<usize>>,
1785    pub pane_history: BTreeMap<ClientId, Vec<PaneId>>,
1786    pub creation_time: Duration,
1787}
1788
1789#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
1790pub struct PluginInfo {
1791    pub location: String,
1792    pub configuration: BTreeMap<String, String>,
1793}
1794
1795impl From<RunPlugin> for PluginInfo {
1796    fn from(run_plugin: RunPlugin) -> Self {
1797        PluginInfo {
1798            location: run_plugin.location.display(),
1799            configuration: run_plugin.configuration.inner().clone(),
1800        }
1801    }
1802}
1803
1804#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1805pub enum LayoutInfo {
1806    BuiltIn(String),
1807    File(String, LayoutMetadata),
1808    Url(String),
1809    Stringified(String),
1810}
1811
1812#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1813pub struct LayoutWithError {
1814    pub layout_name: String,
1815    pub error: LayoutParsingError,
1816}
1817
1818#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1819pub enum LayoutParsingError {
1820    KdlError {
1821        kdl_error: KdlError,
1822        file_name: String,
1823        source_code: String,
1824    },
1825    SyntaxError,
1826}
1827
1828impl AsRef<LayoutInfo> for LayoutInfo {
1829    fn as_ref(&self) -> &LayoutInfo {
1830        self
1831    }
1832}
1833
1834#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
1835pub struct LayoutMetadata {
1836    pub tabs: Vec<TabMetadata>,
1837    pub creation_time: String,
1838    pub update_time: String,
1839}
1840
1841impl From<&PathBuf> for LayoutMetadata {
1842    fn from(path: &PathBuf) -> LayoutMetadata {
1843        match Layout::stringified_from_path(path) {
1844            Ok((path_str, stringified_layout, _swap_layouts)) => {
1845                match Layout::from_kdl(&stringified_layout, Some(path_str), None, None) {
1846                    Ok(layout) => {
1847                        let layout_tabs = layout.tabs();
1848                        let tabs = if layout_tabs.is_empty() {
1849                            let (tiled_pane_layout, floating_pane_layout) = layout.new_tab();
1850                            vec![TabMetadata::from(&(
1851                                None,
1852                                tiled_pane_layout,
1853                                floating_pane_layout,
1854                            ))]
1855                        } else {
1856                            layout
1857                                .tabs()
1858                                .into_iter()
1859                                .map(|tab| TabMetadata::from(&tab))
1860                                .collect()
1861                        };
1862
1863                        // Get file metadata for creation and modification times as Unix epochs
1864                        let (creation_time, update_time) =
1865                            LayoutMetadata::creation_and_update_times(&path);
1866
1867                        LayoutMetadata {
1868                            tabs,
1869                            creation_time,
1870                            update_time,
1871                        }
1872                    },
1873                    Err(e) => {
1874                        log::error!("Failed to parse layout: {}", e);
1875                        LayoutMetadata::default()
1876                    },
1877                }
1878            },
1879            Err(e) => {
1880                log::error!("Failed to read layout file: {}", e);
1881                LayoutMetadata::default()
1882            },
1883        }
1884    }
1885}
1886
1887impl LayoutMetadata {
1888    fn creation_and_update_times(path: &PathBuf) -> (String, String) {
1889        // (creation_time, update_time) returns stringified unix epoch
1890        match std::fs::metadata(path) {
1891            Ok(metadata) => {
1892                let creation_time = metadata
1893                    .created()
1894                    .ok()
1895                    .and_then(|t| {
1896                        t.duration_since(std::time::UNIX_EPOCH)
1897                            .ok()
1898                            .map(|d| d.as_secs().to_string())
1899                    })
1900                    .unwrap_or_default();
1901
1902                let update_time = metadata
1903                    .modified()
1904                    .ok()
1905                    .and_then(|t| {
1906                        t.duration_since(std::time::UNIX_EPOCH)
1907                            .ok()
1908                            .map(|d| d.as_secs().to_string())
1909                    })
1910                    .unwrap_or_default();
1911
1912                (creation_time, update_time)
1913            },
1914            Err(_) => (String::new(), String::new()),
1915        }
1916    }
1917}
1918
1919#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1920pub struct TabMetadata {
1921    pub panes: Vec<PaneMetadata>,
1922    pub name: Option<String>,
1923}
1924
1925impl
1926    From<&(
1927        Option<String>,
1928        crate::input::layout::TiledPaneLayout,
1929        Vec<crate::input::layout::FloatingPaneLayout>,
1930    )> for TabMetadata
1931{
1932    fn from(
1933        tab: &(
1934            Option<String>,
1935            crate::input::layout::TiledPaneLayout,
1936            Vec<crate::input::layout::FloatingPaneLayout>,
1937        ),
1938    ) -> Self {
1939        let (tab_name, tiled_pane_layout, floating_panes) = tab;
1940
1941        // Collect panes from tiled layout (only leaf nodes are real panes)
1942        let mut panes = Vec::new();
1943        collect_leaf_panes(&tiled_pane_layout, &mut panes);
1944
1945        // Collect panes from floating panes
1946        for floating_pane in floating_panes {
1947            panes.push(PaneMetadata::from(floating_pane));
1948        }
1949
1950        TabMetadata {
1951            panes,
1952            name: tab_name.clone(),
1953        }
1954    }
1955}
1956
1957#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1958pub struct PaneMetadata {
1959    pub name: Option<String>,
1960    pub is_plugin: bool,
1961    pub is_builtin_plugin: bool,
1962}
1963
1964impl From<&crate::input::layout::TiledPaneLayout> for PaneMetadata {
1965    fn from(pane: &crate::input::layout::TiledPaneLayout) -> Self {
1966        let mut is_plugin = false;
1967        let mut is_builtin_plugin = false;
1968
1969        // Try to get the name from the pane's name field first
1970        let name = if let Some(ref name) = pane.name {
1971            Some(name.clone())
1972        } else if let Some(ref run) = pane.run {
1973            // If no explicit name, glean it from the run configuration
1974            match run {
1975                Run::Command(cmd) => {
1976                    // Use the command name
1977                    Some(cmd.command.to_string_lossy().to_string())
1978                },
1979                Run::EditFile(path, _line, _cwd) => {
1980                    // Use the file name
1981                    path.file_name().map(|n| n.to_string_lossy().to_string())
1982                },
1983                Run::Plugin(plugin) => {
1984                    is_plugin = true;
1985                    is_builtin_plugin = plugin.is_builtin_plugin();
1986                    Some(plugin.location_string())
1987                },
1988                Run::Cwd(_) => None,
1989            }
1990        } else {
1991            None
1992        };
1993
1994        PaneMetadata {
1995            name,
1996            is_plugin,
1997            is_builtin_plugin,
1998        }
1999    }
2000}
2001
2002impl From<&crate::input::layout::FloatingPaneLayout> for PaneMetadata {
2003    fn from(pane: &crate::input::layout::FloatingPaneLayout) -> Self {
2004        let mut is_plugin = false;
2005        let mut is_builtin_plugin = false;
2006
2007        // Try to get the name from the pane's name field first
2008        let name = if let Some(ref name) = pane.name {
2009            Some(name.clone())
2010        } else if let Some(ref run) = pane.run {
2011            // If no explicit name, glean it from the run configuration
2012            match run {
2013                Run::Command(cmd) => {
2014                    // Use the command name
2015                    Some(cmd.command.to_string_lossy().to_string())
2016                },
2017                Run::EditFile(path, _line, _cwd) => {
2018                    // Use the file name
2019                    path.file_name().map(|n| n.to_string_lossy().to_string())
2020                },
2021                Run::Plugin(plugin) => {
2022                    is_plugin = true;
2023                    is_builtin_plugin = match plugin {
2024                        crate::input::layout::RunPluginOrAlias::RunPlugin(run_plugin) => {
2025                            matches!(run_plugin.location, RunPluginLocation::Zellij(_))
2026                        },
2027                        crate::input::layout::RunPluginOrAlias::Alias(_) => false,
2028                    };
2029                    // Use the plugin location string
2030                    Some(plugin.location_string())
2031                },
2032                Run::Cwd(_) => None,
2033            }
2034        } else {
2035            None
2036        };
2037
2038        PaneMetadata {
2039            name,
2040            is_plugin,
2041            is_builtin_plugin,
2042        }
2043    }
2044}
2045
2046// Helper function to recursively collect leaf panes from TiledPaneLayout
2047fn collect_leaf_panes(
2048    pane: &crate::input::layout::TiledPaneLayout,
2049    result: &mut Vec<PaneMetadata>,
2050) {
2051    if pane.children.is_empty() {
2052        // This is a leaf node (actual pane)
2053        result.push(PaneMetadata::from(pane));
2054    } else {
2055        // This is a container, recurse into children
2056        for child in &pane.children {
2057            collect_leaf_panes(child, result);
2058        }
2059    }
2060}
2061
2062impl LayoutInfo {
2063    pub fn name(&self) -> &str {
2064        match self {
2065            LayoutInfo::BuiltIn(name) => &name,
2066            LayoutInfo::File(name, _) => &name,
2067            LayoutInfo::Url(url) => &url,
2068            LayoutInfo::Stringified(layout) => &layout,
2069        }
2070    }
2071    pub fn is_builtin(&self) -> bool {
2072        match self {
2073            LayoutInfo::BuiltIn(_name) => true,
2074            LayoutInfo::File(_name, _) => false,
2075            LayoutInfo::Url(_url) => false,
2076            LayoutInfo::Stringified(_stringified) => false,
2077        }
2078    }
2079    pub fn from_cli(
2080        layout_dir: &Option<PathBuf>,
2081        maybe_layout_path: &Option<PathBuf>,
2082        cwd: PathBuf,
2083    ) -> Option<Self> {
2084        // If we're not given a layout path, fall back to "default". Since we cannot tell ahead of
2085        // time whether the user has a layout named "default.kdl" in their layout directory, we
2086        // cannot blindly assume that this is indeed the builtin default layout. The layout
2087        // resolution below will correctly handle this.
2088        // The docs promise this behavior, so we have to abide:
2089        // <https://zellij.dev/documentation/layouts.html#layout-default-directory>
2090        let layout_path = maybe_layout_path
2091            .clone()
2092            .unwrap_or(PathBuf::from("default"));
2093
2094        if layout_path.starts_with("http://") || layout_path.starts_with("https://") {
2095            Some(LayoutInfo::Url(layout_path.display().to_string()))
2096        } else if layout_path.extension().is_some() || layout_path.components().count() > 1 {
2097            let layout_dir = cwd;
2098            let file_path = layout_dir.join(layout_path);
2099            Some(LayoutInfo::File(
2100                // layout_dir.join(layout_path).display().to_string(),
2101                file_path.display().to_string(),
2102                LayoutMetadata::from(&file_path),
2103            ))
2104        } else {
2105            // Attempt to interpret the layout as bare layout name from the layout application
2106            // directory. This is described in the docs:
2107            // <https://zellij.dev/documentation/layouts.html#layout-default-directory>
2108            if let Some(layout_dir) = layout_dir
2109                .as_ref()
2110                .map(|l| l.clone())
2111                .or_else(default_layout_dir)
2112            {
2113                let file_path = layout_dir.join(&layout_path);
2114                if file_path.exists() {
2115                    return Some(LayoutInfo::File(
2116                        file_path.display().to_string(),
2117                        LayoutMetadata::from(&file_path),
2118                    ));
2119                }
2120                let file_path_with_ext = file_path.with_extension("kdl");
2121                if file_path_with_ext.exists() {
2122                    return Some(LayoutInfo::File(
2123                        file_path_with_ext.display().to_string(),
2124                        LayoutMetadata::from(&file_path_with_ext),
2125                    ));
2126                }
2127            }
2128            // Assume a builtin layout by default
2129            Some(LayoutInfo::BuiltIn(layout_path.display().to_string()))
2130        }
2131    }
2132    pub fn from_config(
2133        layout_dir: &Option<PathBuf>,
2134        maybe_layout_path: &Option<PathBuf>,
2135    ) -> Option<Self> {
2136        // If we're not given a layout path, fall back to "default". Since we cannot tell ahead of
2137        // time whether the user has a layout named "default.kdl" in their layout directory, we
2138        // cannot blindly assume that this is indeed the builtin default layout. The layout
2139        // resolution below will correctly handle this.
2140        // The docs promise this behavior, so we have to abide:
2141        // <https://zellij.dev/documentation/layouts.html#layout-default-directory>
2142        let layout_path = maybe_layout_path
2143            .clone()
2144            .unwrap_or(PathBuf::from("default"));
2145
2146        if layout_path.starts_with("http://") || layout_path.starts_with("https://") {
2147            Some(LayoutInfo::Url(layout_path.display().to_string()))
2148        } else if layout_path.extension().is_some() || layout_path.components().count() > 1 {
2149            let Some(layout_dir) = layout_dir
2150                .as_ref()
2151                .map(|l| l.clone())
2152                .or_else(default_layout_dir)
2153            else {
2154                return None;
2155            };
2156            let file_path = layout_dir.join(layout_path);
2157            Some(LayoutInfo::File(
2158                // layout_dir.join(layout_path).display().to_string(),
2159                file_path.display().to_string(),
2160                LayoutMetadata::from(&file_path),
2161            ))
2162        } else {
2163            // Attempt to interpret the layout as bare layout name from the layout application
2164            // directory. This is described in the docs:
2165            // <https://zellij.dev/documentation/layouts.html#layout-default-directory>
2166            if let Some(layout_dir) = layout_dir
2167                .as_ref()
2168                .map(|l| l.clone())
2169                .or_else(default_layout_dir)
2170            {
2171                let file_path = layout_dir.join(&layout_path);
2172                if file_path.exists() {
2173                    return Some(LayoutInfo::File(
2174                        file_path.display().to_string(),
2175                        LayoutMetadata::from(&file_path),
2176                    ));
2177                }
2178                let file_path_with_ext = file_path.with_extension("kdl");
2179                if file_path_with_ext.exists() {
2180                    return Some(LayoutInfo::File(
2181                        file_path_with_ext.display().to_string(),
2182                        LayoutMetadata::from(&file_path_with_ext),
2183                    ));
2184                }
2185            }
2186            // Assume a builtin layout by default
2187            Some(LayoutInfo::BuiltIn(layout_path.display().to_string()))
2188        }
2189    }
2190}
2191
2192#[allow(clippy::derive_hash_xor_eq)]
2193impl Hash for SessionInfo {
2194    fn hash<H: Hasher>(&self, state: &mut H) {
2195        self.name.hash(state);
2196    }
2197}
2198
2199impl SessionInfo {
2200    pub fn new(name: String) -> Self {
2201        SessionInfo {
2202            name,
2203            ..Default::default()
2204        }
2205    }
2206    pub fn update_tab_info(&mut self, new_tab_info: Vec<TabInfo>) {
2207        self.tabs = new_tab_info;
2208    }
2209    pub fn update_pane_info(&mut self, new_pane_info: PaneManifest) {
2210        self.panes = new_pane_info;
2211    }
2212    pub fn update_connected_clients(&mut self, new_connected_clients: usize) {
2213        self.connected_clients = new_connected_clients;
2214    }
2215    pub fn populate_plugin_list(&mut self, plugins: BTreeMap<u32, RunPlugin>) {
2216        // u32 - plugin_id
2217        let mut plugin_list = BTreeMap::new();
2218        for (plugin_id, run_plugin) in plugins {
2219            plugin_list.insert(plugin_id, run_plugin.into());
2220        }
2221        self.plugins = plugin_list;
2222    }
2223}
2224
2225/// Contains all the information for a currently opened tab.
2226#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
2227pub struct TabInfo {
2228    /// The Tab's 0 indexed position
2229    pub position: usize,
2230    /// The name of the tab as it appears in the UI (if there's enough room for it)
2231    pub name: String,
2232    /// Whether this tab is focused
2233    pub active: bool,
2234    /// The number of suppressed panes this tab has
2235    pub panes_to_hide: usize,
2236    /// Whether there's one pane taking up the whole display area on this tab
2237    pub is_fullscreen_active: bool,
2238    /// Whether input sent to this tab will be synced to all panes in it
2239    pub is_sync_panes_active: bool,
2240    pub are_floating_panes_visible: bool,
2241    pub other_focused_clients: Vec<ClientId>,
2242    pub active_swap_layout_name: Option<String>,
2243    /// Whether the user manually changed the layout, moving out of the swap layout scheme
2244    pub is_swap_layout_dirty: bool,
2245    /// Row count in the viewport (including all non-ui panes, eg. will excluse the status bar)
2246    pub viewport_rows: usize,
2247    /// Column count in the viewport (including all non-ui panes, eg. will excluse the status bar)
2248    pub viewport_columns: usize,
2249    /// Row count in the display area (including all panes, will typically be larger than the
2250    /// viewport)
2251    pub display_area_rows: usize,
2252    /// Column count in the display area (including all panes, will typically be larger than the
2253    /// viewport)
2254    pub display_area_columns: usize,
2255    /// The number of selectable (eg. not the UI bars) tiled panes currently in this tab
2256    pub selectable_tiled_panes_count: usize,
2257    /// The number of selectable (eg. not the UI bars) floating panes currently in this tab
2258    pub selectable_floating_panes_count: usize,
2259    /// The stable identifier for this tab
2260    pub tab_id: usize,
2261    /// Whether this tab has an active (persistent) bell notification
2262    pub has_bell_notification: bool,
2263    /// Whether this tab is currently flashing its bell (transient 400ms state)
2264    pub is_flashing_bell: bool,
2265}
2266
2267/// The `PaneManifest` contains a dictionary of panes, indexed by the tab position (0 indexed).
2268/// Panes include all panes in the relevant tab, including `tiled` panes, `floating` panes and
2269/// `suppressed` panes.
2270#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
2271pub struct PaneManifest {
2272    pub panes: HashMap<usize, Vec<PaneInfo>>, // usize is the tab position
2273}
2274
2275/// Contains all the information for a currently open pane
2276///
2277/// # Difference between coordinates/size and content coordinates/size
2278///
2279/// The pane basic coordinates and size (eg. `pane_x` or `pane_columns`) are the entire space taken
2280/// up by this pane - including its frame and title if it has a border.
2281///
2282/// The pane content coordinates and size (eg. `pane_content_x` or `pane_content_columns`)
2283/// represent the area taken by the pane's content, excluding its frame and title if it has a
2284/// border.
2285#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
2286pub struct PaneInfo {
2287    /// The id of the pane, unique to all panes of this kind (eg. id in terminals or id in panes)
2288    pub id: u32,
2289    /// Whether this pane is a plugin (`true`) or a terminal (`false`), used along with `id` can represent a unique pane ID across
2290    /// the running session
2291    pub is_plugin: bool,
2292    /// Whether the pane is focused in its layer (tiled or floating)
2293    pub is_focused: bool,
2294    pub is_fullscreen: bool,
2295    /// Whether a pane is floating or tiled (embedded)
2296    pub is_floating: bool,
2297    /// Whether a pane is suppressed - suppressed panes are not visible to the user, but still run
2298    /// in the background
2299    pub is_suppressed: bool,
2300    /// The full title of the pane as it appears in the UI (if there is room for it)
2301    pub title: String,
2302    /// Whether a pane exited or not, note that most panes close themselves before setting this
2303    /// flag, so this is only relevant to command panes
2304    pub exited: bool,
2305    /// The exit status of a pane if it did exit and is still in the UI
2306    pub exit_status: Option<i32>,
2307    /// A "held" pane is a paused pane that is waiting for user input (eg. a command pane that
2308    /// exited and is waiting to be re-run or closed)
2309    pub is_held: bool,
2310    pub pane_x: usize,
2311    pub pane_content_x: usize,
2312    pub pane_y: usize,
2313    pub pane_content_y: usize,
2314    pub pane_rows: usize,
2315    pub pane_content_rows: usize,
2316    pub pane_columns: usize,
2317    pub pane_content_columns: usize,
2318    /// The coordinates of the cursor - if this pane is focused - relative to the pane's
2319    /// coordinates
2320    pub cursor_coordinates_in_pane: Option<(usize, usize)>, // x, y if cursor is visible
2321    /// If this is a command pane, this will show the stringified version of the command and its
2322    /// arguments
2323    pub terminal_command: Option<String>,
2324    /// The URL from which this plugin was loaded (eg. `zellij:strider` for the built-in `strider`
2325    /// plugin or `file:/path/to/my/plugin.wasm` for a local plugin)
2326    pub plugin_url: Option<String>,
2327    /// Unselectable panes are often used for UI elements that do not have direct user interaction
2328    /// (eg. the default `status-bar` or `tab-bar`).
2329    pub is_selectable: bool,
2330    /// Grouped panes (usually through an explicit user action) that are staged for a bulk action
2331    /// the index is kept track of in order to preserve the pane group order
2332    pub index_in_pane_group: BTreeMap<ClientId, usize>,
2333    /// The default foreground color of this pane, if set (e.g. "#00e000")
2334    pub default_fg: Option<String>,
2335    /// The default background color of this pane, if set (e.g. "#001a3a")
2336    pub default_bg: Option<String>,
2337}
2338
2339#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
2340pub struct PaneListEntry {
2341    #[serde(flatten)]
2342    pub pane_info: PaneInfo,
2343    pub tab_id: usize,
2344    pub tab_position: usize,
2345    pub tab_name: String,
2346    #[serde(skip_serializing_if = "Option::is_none")]
2347    pub pane_command: Option<String>,
2348    #[serde(skip_serializing_if = "Option::is_none")]
2349    pub pane_cwd: Option<String>,
2350}
2351
2352pub type ListPanesResponse = Vec<PaneListEntry>;
2353pub type ListTabsResponse = Vec<TabInfo>;
2354
2355#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
2356pub struct ClientInfo {
2357    pub client_id: ClientId,
2358    pub pane_id: PaneId,
2359    pub running_command: String,
2360    pub is_current_client: bool,
2361}
2362
2363impl ClientInfo {
2364    pub fn new(
2365        client_id: ClientId,
2366        pane_id: PaneId,
2367        running_command: String,
2368        is_current_client: bool,
2369    ) -> Self {
2370        ClientInfo {
2371            client_id,
2372            pane_id,
2373            running_command,
2374            is_current_client,
2375        }
2376    }
2377}
2378
2379#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
2380pub struct PaneRenderReport {
2381    pub all_pane_contents: HashMap<ClientId, HashMap<PaneId, PaneContents>>,
2382    pub all_pane_contents_with_ansi: HashMap<ClientId, HashMap<PaneId, PaneContents>>,
2383}
2384
2385impl PaneRenderReport {
2386    pub fn add_pane_contents(
2387        &mut self,
2388        client_ids: &[ClientId],
2389        pane_id: PaneId,
2390        pane_contents: PaneContents,
2391    ) {
2392        for client_id in client_ids {
2393            let p = self
2394                .all_pane_contents
2395                .entry(*client_id)
2396                .or_insert_with(|| HashMap::new());
2397            p.insert(pane_id, pane_contents.clone());
2398        }
2399    }
2400    pub fn add_pane_contents_with_ansi(
2401        &mut self,
2402        client_ids: &[ClientId],
2403        pane_id: PaneId,
2404        pane_contents: PaneContents,
2405    ) {
2406        for client_id in client_ids {
2407            let p = self
2408                .all_pane_contents_with_ansi
2409                .entry(*client_id)
2410                .or_insert_with(|| HashMap::new());
2411            p.insert(pane_id, pane_contents.clone());
2412        }
2413    }
2414}
2415
2416#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
2417pub struct PaneContents {
2418    // NOTE: both lines_above_viewport and lines_below_viewport are only populated if explicitly
2419    // requested (eg. with get_full_scrollback true in the plugin command) this is for performance
2420    // reasons
2421    pub lines_above_viewport: Vec<String>,
2422    pub lines_below_viewport: Vec<String>,
2423    pub viewport: Vec<String>,
2424    pub selected_text: Option<SelectedText>,
2425}
2426
2427/// Extract text from a line between two column positions, accounting for wide characters
2428fn extract_text_by_columns(line: &str, start_col: usize, end_col: usize) -> String {
2429    let mut current_col = 0;
2430    let mut result = String::new();
2431    let mut capturing = false;
2432
2433    for ch in line.chars() {
2434        let char_width = ch.width().unwrap_or(0);
2435
2436        // Start capturing when we reach start_col
2437        if current_col >= start_col && !capturing {
2438            capturing = true;
2439        }
2440
2441        // Stop if we've reached or passed end_col
2442        if current_col >= end_col {
2443            break;
2444        }
2445
2446        // Capture character if we're in the range
2447        if capturing {
2448            result.push(ch);
2449        }
2450
2451        current_col += char_width;
2452    }
2453
2454    result
2455}
2456
2457/// Extract text from a line starting at a column position, accounting for wide characters
2458fn extract_text_from_column(line: &str, start_col: usize) -> String {
2459    let mut current_col = 0;
2460    let mut result = String::new();
2461    let mut capturing = false;
2462
2463    for ch in line.chars() {
2464        let char_width = ch.width().unwrap_or(0);
2465
2466        if current_col >= start_col {
2467            capturing = true;
2468        }
2469
2470        if capturing {
2471            result.push(ch);
2472        }
2473
2474        current_col += char_width;
2475    }
2476
2477    result
2478}
2479
2480/// Extract text from a line up to a column position, accounting for wide characters
2481fn extract_text_to_column(line: &str, end_col: usize) -> String {
2482    let mut current_col = 0;
2483    let mut result = String::new();
2484
2485    for ch in line.chars() {
2486        let char_width = ch.width().unwrap_or(0);
2487
2488        if current_col >= end_col {
2489            break;
2490        }
2491
2492        result.push(ch);
2493        current_col += char_width;
2494    }
2495
2496    result
2497}
2498
2499impl PaneContents {
2500    pub fn new(viewport: Vec<String>, selection_start: Position, selection_end: Position) -> Self {
2501        PaneContents {
2502            viewport,
2503            selected_text: SelectedText::from_positions(selection_start, selection_end),
2504            ..Default::default()
2505        }
2506    }
2507    pub fn new_with_scrollback(
2508        viewport: Vec<String>,
2509        selection_start: Position,
2510        selection_end: Position,
2511        lines_above_viewport: Vec<String>,
2512        lines_below_viewport: Vec<String>,
2513    ) -> Self {
2514        PaneContents {
2515            viewport,
2516            selected_text: SelectedText::from_positions(selection_start, selection_end),
2517            lines_above_viewport,
2518            lines_below_viewport,
2519        }
2520    }
2521
2522    /// Returns the actual text content of the selection, if any exists.
2523    /// Selection only occurs within the viewport.
2524    pub fn get_selected_text(&self) -> Option<String> {
2525        let selected_text = self.selected_text?;
2526
2527        let start_line = selected_text.start.line() as usize;
2528        let start_col = selected_text.start.column();
2529        let end_line = selected_text.end.line() as usize;
2530        let end_col = selected_text.end.column();
2531
2532        // Handle out of bounds
2533        if start_line >= self.viewport.len() || end_line >= self.viewport.len() {
2534            return None;
2535        }
2536
2537        if start_line == end_line {
2538            // Single line selection
2539            let line = &self.viewport[start_line];
2540            Some(extract_text_by_columns(line, start_col, end_col))
2541        } else {
2542            // Multi-line selection
2543            let mut result = String::new();
2544
2545            // First line - from start column to end of line
2546            let first_line = &self.viewport[start_line];
2547            result.push_str(&extract_text_from_column(first_line, start_col));
2548            result.push('\n');
2549
2550            // Middle lines - complete lines
2551            for i in (start_line + 1)..end_line {
2552                result.push_str(&self.viewport[i]);
2553                result.push('\n');
2554            }
2555
2556            // Last line - from start to end column
2557            let last_line = &self.viewport[end_line];
2558            result.push_str(&extract_text_to_column(last_line, end_col));
2559
2560            Some(result)
2561        }
2562    }
2563}
2564
2565#[derive(Debug, Clone, Serialize, Deserialize)]
2566pub enum PaneScrollbackResponse {
2567    Ok(PaneContents),
2568    Err(String),
2569}
2570
2571#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2572pub enum GetPanePidResponse {
2573    Ok(i32),
2574    Err(String),
2575}
2576
2577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2578pub enum GetPaneRunningCommandResponse {
2579    Ok(Vec<String>),
2580    Err(String),
2581}
2582
2583#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2584pub enum GetPaneCwdResponse {
2585    Ok(PathBuf),
2586    Err(String),
2587}
2588
2589#[derive(Debug, Clone, PartialEq)]
2590pub enum GetFocusedPaneInfoResponse {
2591    Ok { tab_index: usize, pane_id: PaneId },
2592    Err(String),
2593}
2594
2595#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2596pub enum SaveLayoutResponse {
2597    Ok(()),
2598    Err(String),
2599}
2600
2601#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2602pub enum DeleteLayoutResponse {
2603    Ok(()),
2604    Err(String),
2605}
2606
2607#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2608pub enum RenameLayoutResponse {
2609    Ok(()),
2610    Err(String),
2611}
2612
2613#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2614pub enum EditLayoutResponse {
2615    Ok(()),
2616    Err(String),
2617}
2618
2619#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2620pub struct SelectedText {
2621    pub start: Position,
2622    pub end: Position,
2623}
2624
2625impl SelectedText {
2626    pub fn new(start: Position, end: Position) -> Self {
2627        // Normalize: ensure start <= end
2628        let (normalized_start, normalized_end) = if start <= end {
2629            (start, end)
2630        } else {
2631            (end, start)
2632        };
2633
2634        // Normalize negative line values to 0
2635        // (column is already usize so can't be negative)
2636        let normalized_start = Position::new(
2637            normalized_start.line().max(0) as i32,
2638            normalized_start.column() as u16,
2639        );
2640        let normalized_end = Position::new(
2641            normalized_end.line().max(0) as i32,
2642            normalized_end.column() as u16,
2643        );
2644
2645        SelectedText {
2646            start: normalized_start,
2647            end: normalized_end,
2648        }
2649    }
2650
2651    pub fn from_positions(start: Position, end: Position) -> Option<Self> {
2652        if start == end {
2653            None
2654        } else {
2655            Some(Self::new(start, end))
2656        }
2657    }
2658}
2659
2660#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
2661pub struct PluginIds {
2662    pub plugin_id: u32,
2663    pub zellij_pid: u32,
2664    pub initial_cwd: PathBuf,
2665    pub client_id: ClientId,
2666}
2667
2668/// Tag used to identify the plugin in layout and config kdl files
2669#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
2670pub struct PluginTag(String);
2671
2672impl PluginTag {
2673    pub fn new(url: impl Into<String>) -> Self {
2674        PluginTag(url.into())
2675    }
2676}
2677
2678impl From<PluginTag> for String {
2679    fn from(tag: PluginTag) -> Self {
2680        tag.0
2681    }
2682}
2683
2684impl fmt::Display for PluginTag {
2685    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2686        write!(f, "{}", self.0)
2687    }
2688}
2689
2690#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
2691pub struct PluginCapabilities {
2692    pub arrow_fonts: bool,
2693}
2694
2695impl Default for PluginCapabilities {
2696    fn default() -> PluginCapabilities {
2697        PluginCapabilities { arrow_fonts: true }
2698    }
2699}
2700
2701/// Represents a Clipboard type
2702#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
2703pub enum CopyDestination {
2704    Command,
2705    Primary,
2706    System,
2707}
2708
2709#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
2710pub enum PermissionStatus {
2711    Granted,
2712    Denied,
2713}
2714
2715#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
2716pub struct FileToOpen {
2717    pub path: PathBuf,
2718    pub line_number: Option<usize>,
2719    pub cwd: Option<PathBuf>,
2720}
2721
2722impl FileToOpen {
2723    pub fn new<P: AsRef<Path>>(path: P) -> Self {
2724        FileToOpen {
2725            path: path.as_ref().to_path_buf(),
2726            ..Default::default()
2727        }
2728    }
2729    pub fn with_line_number(mut self, line_number: usize) -> Self {
2730        self.line_number = Some(line_number);
2731        self
2732    }
2733    pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
2734        self.cwd = Some(cwd);
2735        self
2736    }
2737}
2738
2739#[derive(Debug, Default, Clone)]
2740pub struct CommandToRun {
2741    pub path: PathBuf,
2742    pub args: Vec<String>,
2743    pub cwd: Option<PathBuf>,
2744}
2745
2746impl CommandToRun {
2747    pub fn new<P: AsRef<Path>>(path: P) -> Self {
2748        CommandToRun {
2749            path: path.as_ref().to_path_buf(),
2750            ..Default::default()
2751        }
2752    }
2753    pub fn new_with_args<P: AsRef<Path>, A: AsRef<str>>(path: P, args: Vec<A>) -> Self {
2754        CommandToRun {
2755            path: path.as_ref().to_path_buf(),
2756            args: args.into_iter().map(|a| a.as_ref().to_owned()).collect(),
2757            ..Default::default()
2758        }
2759    }
2760}
2761
2762#[derive(Debug, Default, Clone)]
2763pub struct MessageToPlugin {
2764    pub plugin_url: Option<String>,
2765    pub destination_plugin_id: Option<u32>,
2766    pub plugin_config: BTreeMap<String, String>,
2767    pub message_name: String,
2768    pub message_payload: Option<String>,
2769    pub message_args: BTreeMap<String, String>,
2770    /// these will only be used in case we need to launch a new plugin to send this message to,
2771    /// since none are running
2772    pub new_plugin_args: Option<NewPluginArgs>,
2773    pub floating_pane_coordinates: Option<FloatingPaneCoordinates>,
2774}
2775
2776#[derive(Debug, Default, Clone)]
2777pub struct NewPluginArgs {
2778    pub should_float: Option<bool>,
2779    pub pane_id_to_replace: Option<PaneId>,
2780    pub pane_title: Option<String>,
2781    pub cwd: Option<PathBuf>,
2782    pub skip_cache: bool,
2783    pub should_focus: Option<bool>,
2784}
2785
2786#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
2787pub enum PaneId {
2788    Terminal(u32),
2789    Plugin(u32),
2790}
2791
2792impl Default for PaneId {
2793    fn default() -> Self {
2794        PaneId::Terminal(0)
2795    }
2796}
2797
2798impl FromStr for PaneId {
2799    type Err = Box<dyn std::error::Error>;
2800    fn from_str(stringified_pane_id: &str) -> Result<Self, Self::Err> {
2801        if let Some(terminal_stringified_pane_id) = stringified_pane_id.strip_prefix("terminal_") {
2802            u32::from_str_radix(terminal_stringified_pane_id, 10)
2803                .map(|id| PaneId::Terminal(id))
2804                .map_err(|e| e.into())
2805        } else if let Some(plugin_pane_id) = stringified_pane_id.strip_prefix("plugin_") {
2806            u32::from_str_radix(plugin_pane_id, 10)
2807                .map(|id| PaneId::Plugin(id))
2808                .map_err(|e| e.into())
2809        } else {
2810            u32::from_str_radix(&stringified_pane_id, 10)
2811                .map(|id| PaneId::Terminal(id))
2812                .map_err(|e| e.into())
2813        }
2814    }
2815}
2816
2817impl std::fmt::Display for PaneId {
2818    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2819        match self {
2820            PaneId::Terminal(id) => write!(f, "terminal_{}", id),
2821            PaneId::Plugin(id) => write!(f, "plugin_{}", id),
2822        }
2823    }
2824}
2825
2826impl MessageToPlugin {
2827    pub fn new(message_name: impl Into<String>) -> Self {
2828        MessageToPlugin {
2829            message_name: message_name.into(),
2830            ..Default::default()
2831        }
2832    }
2833    pub fn with_plugin_url(mut self, url: impl Into<String>) -> Self {
2834        self.plugin_url = Some(url.into());
2835        self
2836    }
2837    pub fn with_destination_plugin_id(mut self, destination_plugin_id: u32) -> Self {
2838        self.destination_plugin_id = Some(destination_plugin_id);
2839        self
2840    }
2841    pub fn with_plugin_config(mut self, plugin_config: BTreeMap<String, String>) -> Self {
2842        self.plugin_config = plugin_config;
2843        self
2844    }
2845    pub fn with_payload(mut self, payload: impl Into<String>) -> Self {
2846        self.message_payload = Some(payload.into());
2847        self
2848    }
2849    pub fn with_args(mut self, args: BTreeMap<String, String>) -> Self {
2850        self.message_args = args;
2851        self
2852    }
2853    pub fn with_floating_pane_coordinates(
2854        mut self,
2855        floating_pane_coordinates: FloatingPaneCoordinates,
2856    ) -> Self {
2857        self.floating_pane_coordinates = Some(floating_pane_coordinates);
2858        self
2859    }
2860    pub fn new_plugin_instance_should_float(mut self, should_float: bool) -> Self {
2861        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2862        new_plugin_args.should_float = Some(should_float);
2863        self
2864    }
2865    pub fn new_plugin_instance_should_replace_pane(mut self, pane_id: PaneId) -> Self {
2866        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2867        new_plugin_args.pane_id_to_replace = Some(pane_id);
2868        self
2869    }
2870    pub fn new_plugin_instance_should_have_pane_title(
2871        mut self,
2872        pane_title: impl Into<String>,
2873    ) -> Self {
2874        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2875        new_plugin_args.pane_title = Some(pane_title.into());
2876        self
2877    }
2878    pub fn new_plugin_instance_should_have_cwd(mut self, cwd: PathBuf) -> Self {
2879        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2880        new_plugin_args.cwd = Some(cwd);
2881        self
2882    }
2883    pub fn new_plugin_instance_should_skip_cache(mut self) -> Self {
2884        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2885        new_plugin_args.skip_cache = true;
2886        self
2887    }
2888    pub fn new_plugin_instance_should_be_focused(mut self) -> Self {
2889        let new_plugin_args = self.new_plugin_args.get_or_insert_with(Default::default);
2890        new_plugin_args.should_focus = Some(true);
2891        self
2892    }
2893    pub fn has_cwd(&self) -> bool {
2894        self.new_plugin_args
2895            .as_ref()
2896            .map(|n| n.cwd.is_some())
2897            .unwrap_or(false)
2898    }
2899}
2900
2901#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
2902pub struct ConnectToSession {
2903    pub name: Option<String>,
2904    pub tab_position: Option<usize>,
2905    pub pane_id: Option<(u32, bool)>, // (id, is_plugin)
2906    pub layout: Option<LayoutInfo>,
2907    pub cwd: Option<PathBuf>,
2908}
2909
2910impl ConnectToSession {
2911    pub fn apply_layout_dir(&mut self, layout_dir: &PathBuf) {
2912        if let Some(LayoutInfo::File(file_path, _layout_metadata)) = self.layout.as_mut() {
2913            *file_path = Path::join(layout_dir, &file_path)
2914                .to_string_lossy()
2915                .to_string();
2916        }
2917    }
2918}
2919
2920#[derive(Debug, Default, Clone)]
2921pub struct PluginMessage {
2922    pub name: String,
2923    pub payload: String,
2924    pub worker_name: Option<String>,
2925}
2926
2927impl PluginMessage {
2928    pub fn new_to_worker(worker_name: &str, message: &str, payload: &str) -> Self {
2929        PluginMessage {
2930            name: message.to_owned(),
2931            payload: payload.to_owned(),
2932            worker_name: Some(worker_name.to_owned()),
2933        }
2934    }
2935    pub fn new_to_plugin(message: &str, payload: &str) -> Self {
2936        PluginMessage {
2937            name: message.to_owned(),
2938            payload: payload.to_owned(),
2939            worker_name: None,
2940        }
2941    }
2942}
2943
2944#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2945pub enum HttpVerb {
2946    Get,
2947    Post,
2948    Put,
2949    Delete,
2950}
2951
2952#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2953pub enum PipeSource {
2954    Cli(String), // String is the pipe_id of the CLI pipe (used for blocking/unblocking)
2955    Plugin(u32), // u32 is the lugin id
2956    Keybind,     // TODO: consider including the actual keybind here?
2957}
2958
2959#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2960pub struct PipeMessage {
2961    pub source: PipeSource,
2962    pub name: String,
2963    pub payload: Option<String>,
2964    pub args: BTreeMap<String, String>,
2965    pub is_private: bool,
2966}
2967
2968impl PipeMessage {
2969    pub fn new(
2970        source: PipeSource,
2971        name: impl Into<String>,
2972        payload: &Option<String>,
2973        args: &Option<BTreeMap<String, String>>,
2974        is_private: bool,
2975    ) -> Self {
2976        PipeMessage {
2977            source,
2978            name: name.into(),
2979            payload: payload.clone(),
2980            args: args.clone().unwrap_or_else(|| Default::default()),
2981            is_private,
2982        }
2983    }
2984}
2985
2986#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
2987pub struct FloatingPaneCoordinates {
2988    pub x: Option<PercentOrFixed>,
2989    pub y: Option<PercentOrFixed>,
2990    pub width: Option<PercentOrFixed>,
2991    pub height: Option<PercentOrFixed>,
2992    pub pinned: Option<bool>,
2993    pub borderless: Option<bool>,
2994}
2995
2996impl FloatingPaneCoordinates {
2997    pub fn new(
2998        x: Option<String>,
2999        y: Option<String>,
3000        width: Option<String>,
3001        height: Option<String>,
3002        pinned: Option<bool>,
3003        borderless: Option<bool>,
3004    ) -> Option<Self> {
3005        // Parse x/y coordinates - allows 0% or 0
3006        let x = x.and_then(|x| PercentOrFixed::from_str(&x).ok());
3007        let y = y.and_then(|y| PercentOrFixed::from_str(&y).ok());
3008
3009        // Parse width/height - reject 0% or 0
3010        let width = width.and_then(|w| {
3011            PercentOrFixed::from_str(&w)
3012                .ok()
3013                .and_then(|size| match size {
3014                    PercentOrFixed::Percent(0) => None,
3015                    PercentOrFixed::Fixed(0) => None,
3016                    _ => Some(size),
3017                })
3018        });
3019        let height = height.and_then(|h| {
3020            PercentOrFixed::from_str(&h)
3021                .ok()
3022                .and_then(|size| match size {
3023                    PercentOrFixed::Percent(0) => None,
3024                    PercentOrFixed::Fixed(0) => None,
3025                    _ => Some(size),
3026                })
3027        });
3028
3029        if x.is_none()
3030            && y.is_none()
3031            && width.is_none()
3032            && height.is_none()
3033            && pinned.is_none()
3034            && borderless.is_none()
3035        {
3036            None
3037        } else {
3038            Some(FloatingPaneCoordinates {
3039                x,
3040                y,
3041                width,
3042                height,
3043                pinned,
3044                borderless,
3045            })
3046        }
3047    }
3048    pub fn with_x_fixed(mut self, x: usize) -> Self {
3049        self.x = Some(PercentOrFixed::Fixed(x));
3050        self
3051    }
3052    pub fn with_x_percent(mut self, x: usize) -> Self {
3053        if x > 100 {
3054            eprintln!("x must be between 0 and 100");
3055            return self;
3056        }
3057        self.x = Some(PercentOrFixed::Percent(x));
3058        self
3059    }
3060    pub fn with_y_fixed(mut self, y: usize) -> Self {
3061        self.y = Some(PercentOrFixed::Fixed(y));
3062        self
3063    }
3064    pub fn with_y_percent(mut self, y: usize) -> Self {
3065        if y > 100 {
3066            eprintln!("y must be between 0 and 100");
3067            return self;
3068        }
3069        self.y = Some(PercentOrFixed::Percent(y));
3070        self
3071    }
3072    pub fn with_width_fixed(mut self, width: usize) -> Self {
3073        self.width = Some(PercentOrFixed::Fixed(width));
3074        self
3075    }
3076    pub fn with_width_percent(mut self, width: usize) -> Self {
3077        if width > 100 {
3078            eprintln!("width must be between 0 and 100");
3079            return self;
3080        }
3081        self.width = Some(PercentOrFixed::Percent(width));
3082        self
3083    }
3084    pub fn with_height_fixed(mut self, height: usize) -> Self {
3085        self.height = Some(PercentOrFixed::Fixed(height));
3086        self
3087    }
3088    pub fn with_height_percent(mut self, height: usize) -> Self {
3089        if height > 100 {
3090            eprintln!("height must be between 0 and 100");
3091            return self;
3092        }
3093        self.height = Some(PercentOrFixed::Percent(height));
3094        self
3095    }
3096}
3097
3098impl From<PaneGeom> for FloatingPaneCoordinates {
3099    fn from(pane_geom: PaneGeom) -> Self {
3100        FloatingPaneCoordinates {
3101            x: Some(PercentOrFixed::Fixed(pane_geom.x)),
3102            y: Some(PercentOrFixed::Fixed(pane_geom.y)),
3103            width: Some(PercentOrFixed::Fixed(pane_geom.cols.as_usize())),
3104            height: Some(PercentOrFixed::Fixed(pane_geom.rows.as_usize())),
3105            pinned: Some(pane_geom.is_pinned),
3106            borderless: None,
3107        }
3108    }
3109}
3110
3111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3112pub struct OriginatingPlugin {
3113    pub plugin_id: u32,
3114    pub client_id: ClientId,
3115    pub context: Context,
3116}
3117
3118impl OriginatingPlugin {
3119    pub fn new(plugin_id: u32, client_id: ClientId, context: Context) -> Self {
3120        OriginatingPlugin {
3121            plugin_id,
3122            client_id,
3123            context,
3124        }
3125    }
3126}
3127
3128#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
3129pub enum WebSharing {
3130    #[serde(alias = "on")]
3131    On,
3132    #[serde(alias = "off")]
3133    Off,
3134    #[serde(alias = "disabled")]
3135    Disabled,
3136}
3137
3138impl Default for WebSharing {
3139    fn default() -> Self {
3140        Self::Off
3141    }
3142}
3143
3144impl WebSharing {
3145    pub fn is_on(&self) -> bool {
3146        match self {
3147            WebSharing::On => true,
3148            _ => false,
3149        }
3150    }
3151    pub fn web_clients_allowed(&self) -> bool {
3152        match self {
3153            WebSharing::On => true,
3154            _ => false,
3155        }
3156    }
3157    pub fn sharing_is_disabled(&self) -> bool {
3158        match self {
3159            WebSharing::Disabled => true,
3160            _ => false,
3161        }
3162    }
3163    pub fn set_sharing(&mut self) -> bool {
3164        // returns true if successfully set sharing
3165        match self {
3166            WebSharing::On => true,
3167            WebSharing::Off => {
3168                *self = WebSharing::On;
3169                true
3170            },
3171            WebSharing::Disabled => false,
3172        }
3173    }
3174    pub fn set_not_sharing(&mut self) -> bool {
3175        // returns true if successfully set not sharing
3176        match self {
3177            WebSharing::On => {
3178                *self = WebSharing::Off;
3179                true
3180            },
3181            WebSharing::Off => true,
3182            WebSharing::Disabled => false,
3183        }
3184    }
3185}
3186
3187impl FromStr for WebSharing {
3188    type Err = String;
3189    fn from_str(s: &str) -> Result<Self, Self::Err> {
3190        match s {
3191            "On" | "on" => Ok(Self::On),
3192            "Off" | "off" => Ok(Self::Off),
3193            "Disabled" | "disabled" => Ok(Self::Disabled),
3194            _ => Err(format!("No such option: {}", s)),
3195        }
3196    }
3197}
3198
3199#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
3200pub enum NewPanePlacement {
3201    NoPreference {
3202        borderless: Option<bool>,
3203    },
3204    Tiled {
3205        direction: Option<Direction>,
3206        borderless: Option<bool>,
3207    },
3208    Floating(Option<FloatingPaneCoordinates>),
3209    InPlace {
3210        pane_id_to_replace: Option<PaneId>,
3211        close_replaced_pane: bool,
3212        borderless: Option<bool>,
3213    },
3214    Stacked {
3215        pane_id_to_stack_under: Option<PaneId>,
3216        borderless: Option<bool>,
3217    },
3218}
3219
3220impl Default for NewPanePlacement {
3221    fn default() -> Self {
3222        NewPanePlacement::NoPreference { borderless: None }
3223    }
3224}
3225
3226impl NewPanePlacement {
3227    pub fn with_floating_pane_coordinates(
3228        floating_pane_coordinates: Option<FloatingPaneCoordinates>,
3229    ) -> Self {
3230        NewPanePlacement::Floating(floating_pane_coordinates)
3231    }
3232    pub fn with_should_be_in_place(
3233        self,
3234        should_be_in_place: bool,
3235        close_replaced_pane: bool,
3236    ) -> Self {
3237        if should_be_in_place {
3238            NewPanePlacement::InPlace {
3239                pane_id_to_replace: None,
3240                close_replaced_pane,
3241                borderless: None,
3242            }
3243        } else {
3244            self
3245        }
3246    }
3247    pub fn with_pane_id_to_replace(
3248        pane_id_to_replace: Option<PaneId>,
3249        close_replaced_pane: bool,
3250    ) -> Self {
3251        NewPanePlacement::InPlace {
3252            pane_id_to_replace,
3253            close_replaced_pane,
3254            borderless: None,
3255        }
3256    }
3257    pub fn should_float(&self) -> Option<bool> {
3258        match self {
3259            NewPanePlacement::Floating(_) => Some(true),
3260            NewPanePlacement::Tiled { .. } => Some(false),
3261            _ => None,
3262        }
3263    }
3264    pub fn floating_pane_coordinates(&self) -> Option<FloatingPaneCoordinates> {
3265        match self {
3266            NewPanePlacement::Floating(floating_pane_coordinates) => {
3267                floating_pane_coordinates.clone()
3268            },
3269            _ => None,
3270        }
3271    }
3272    pub fn should_stack(&self) -> bool {
3273        match self {
3274            NewPanePlacement::Stacked { .. } => true,
3275            _ => false,
3276        }
3277    }
3278    pub fn id_of_stack_root(&self) -> Option<PaneId> {
3279        match self {
3280            NewPanePlacement::Stacked {
3281                pane_id_to_stack_under,
3282                ..
3283            } => *pane_id_to_stack_under,
3284            _ => None,
3285        }
3286    }
3287    pub fn get_borderless(&self) -> Option<bool> {
3288        match self {
3289            NewPanePlacement::NoPreference { borderless } => *borderless,
3290            NewPanePlacement::Tiled { borderless, .. } => *borderless,
3291            NewPanePlacement::Floating(coords) => coords.as_ref().and_then(|c| c.borderless),
3292            NewPanePlacement::InPlace { borderless, .. } => *borderless,
3293            NewPanePlacement::Stacked { borderless, .. } => *borderless,
3294        }
3295    }
3296}
3297
3298type Context = BTreeMap<String, String>;
3299
3300#[derive(Debug, Clone, EnumDiscriminants, Display)]
3301#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
3302#[strum_discriminants(name(CommandType))]
3303pub enum PluginCommand {
3304    Subscribe(HashSet<EventType>),
3305    Unsubscribe(HashSet<EventType>),
3306    SetSelectable(bool),
3307    ShowCursor(Option<(usize, usize)>),
3308    GetPluginIds,
3309    GetZellijVersion,
3310    OpenFile(FileToOpen, Context),
3311    OpenFileFloating(FileToOpen, Option<FloatingPaneCoordinates>, Context),
3312    OpenTerminal(FileToOpen), // only used for the path as cwd
3313    OpenTerminalFloating(FileToOpen, Option<FloatingPaneCoordinates>), // only used for the path as cwd
3314    OpenCommandPane(CommandToRun, Context),
3315    OpenCommandPaneFloating(CommandToRun, Option<FloatingPaneCoordinates>, Context),
3316    SwitchTabTo(u32), // tab index
3317    SetTimeout(f64),  // seconds
3318    ExecCmd(Vec<String>),
3319    PostMessageTo(PluginMessage),
3320    PostMessageToPlugin(PluginMessage),
3321    HideSelf,
3322    ShowSelf(bool), // bool - should float if hidden
3323    SwitchToMode(InputMode),
3324    NewTabsWithLayout(String), // raw kdl layout
3325    NewTab {
3326        name: Option<String>,
3327        cwd: Option<String>,
3328    },
3329    GoToNextTab,
3330    GoToPreviousTab,
3331    Resize(Resize),
3332    ResizeWithDirection(ResizeStrategy),
3333    FocusNextPane,
3334    FocusPreviousPane,
3335    MoveFocus(Direction),
3336    MoveFocusOrTab(Direction),
3337    Detach,
3338    EditScrollback,
3339    Write(Vec<u8>), // bytes
3340    WriteChars(String),
3341    ToggleTab,
3342    MovePane,
3343    MovePaneWithDirection(Direction),
3344    ClearScreen,
3345    ScrollUp,
3346    ScrollDown,
3347    ScrollToTop,
3348    ScrollToBottom,
3349    PageScrollUp,
3350    PageScrollDown,
3351    ToggleFocusFullscreen,
3352    TogglePaneFrames,
3353    TogglePaneEmbedOrEject,
3354    UndoRenamePane,
3355    CloseFocus,
3356    ToggleActiveTabSync,
3357    CloseFocusedTab,
3358    UndoRenameTab,
3359    QuitZellij,
3360    PreviousSwapLayout,
3361    NextSwapLayout,
3362    GoToTabName(String),
3363    FocusOrCreateTab(String),
3364    GoToTab(u32),                       // tab index
3365    StartOrReloadPlugin(String),        // plugin url (eg. file:/path/to/plugin.wasm)
3366    CloseTerminalPane(u32),             // terminal pane id
3367    ClosePluginPane(u32),               // plugin pane id
3368    FocusTerminalPane(u32, bool, bool), // terminal pane id, should_float_if_hidden, should_be_in_place_if_hidden
3369    FocusPluginPane(u32, bool, bool), // plugin pane id, should_float_if_hidden, should_be_in_place_if_hidden
3370    RenameTerminalPane(u32, String),  // terminal pane id, new name
3371    RenamePluginPane(u32, String),    // plugin pane id, new name
3372    RenameTab(u32, String),           // tab index, new name
3373    ReportPanic(String),              // stringified panic
3374    RequestPluginPermissions(Vec<PermissionType>),
3375    SwitchSession(ConnectToSession),
3376    DeleteDeadSession(String),       // String -> session name
3377    DeleteAllDeadSessions,           // String -> session name
3378    OpenTerminalInPlace(FileToOpen), // only used for the path as cwd
3379    OpenFileInPlace(FileToOpen, Context),
3380    OpenCommandPaneInPlace(CommandToRun, Context),
3381    RunCommand(
3382        Vec<String>,              // command
3383        BTreeMap<String, String>, // env_variables
3384        PathBuf,                  // cwd
3385        BTreeMap<String, String>, // context
3386    ),
3387    WebRequest(
3388        String, // url
3389        HttpVerb,
3390        BTreeMap<String, String>, // headers
3391        Vec<u8>,                  // body
3392        BTreeMap<String, String>, // context
3393    ),
3394    RenameSession(String),         // String -> new session name
3395    UnblockCliPipeInput(String),   // String => pipe name
3396    BlockCliPipeInput(String),     // String => pipe name
3397    CliPipeOutput(String, String), // String => pipe name, String => output
3398    MessageToPlugin(MessageToPlugin),
3399    DisconnectOtherClients,
3400    KillSessions(Vec<String>), // one or more session names
3401    ScanHostFolder(PathBuf),   // TODO: rename to ScanHostFolder
3402    WatchFilesystem,
3403    DumpSessionLayout {
3404        tab_index: Option<usize>,
3405    },
3406    CloseSelf,
3407    NewTabsWithLayoutInfo(LayoutInfo),
3408    Reconfigure(String, bool), // String -> stringified configuration, bool -> save configuration
3409    // file to disk
3410    HidePaneWithId(PaneId),
3411    ShowPaneWithId(PaneId, bool, bool), // bools -> should_float_if_hidden, should_focus_pane
3412    OpenCommandPaneBackground(CommandToRun, Context),
3413    RerunCommandPane(u32), // u32  - terminal pane id
3414    ResizePaneIdWithDirection(ResizeStrategy, PaneId),
3415    EditScrollbackForPaneWithId(PaneId),
3416    GetPaneScrollback {
3417        pane_id: PaneId,
3418        get_full_scrollback: bool,
3419    },
3420    WriteToPaneId(Vec<u8>, PaneId),
3421    WriteCharsToPaneId(String, PaneId),
3422    SendSigintToPaneId(PaneId),
3423    SendSigkillToPaneId(PaneId),
3424    GetPanePid {
3425        pane_id: PaneId,
3426    },
3427    GetPaneRunningCommand {
3428        pane_id: PaneId,
3429    },
3430    GetPaneCwd {
3431        pane_id: PaneId,
3432    },
3433    MovePaneWithPaneId(PaneId),
3434    MovePaneWithPaneIdInDirection(PaneId, Direction),
3435    ClearScreenForPaneId(PaneId),
3436    ScrollUpInPaneId(PaneId),
3437    ScrollDownInPaneId(PaneId),
3438    ScrollToTopInPaneId(PaneId),
3439    ScrollToBottomInPaneId(PaneId),
3440    PageScrollUpInPaneId(PaneId),
3441    PageScrollDownInPaneId(PaneId),
3442    TogglePaneIdFullscreen(PaneId),
3443    TogglePaneEmbedOrEjectForPaneId(PaneId),
3444    CloseTabWithIndex(usize), // usize - tab_index
3445    BreakPanesToNewTab(Vec<PaneId>, Option<String>, bool), // bool -
3446    // should_change_focus_to_new_tab,
3447    // Option<String> - optional name for
3448    // the new tab
3449    BreakPanesToTabWithIndex(Vec<PaneId>, usize, bool), // usize - tab_index, bool -
3450    // should_change_focus_to_new_tab
3451    SwitchTabToId(u64),                            // u64 - tab_id
3452    GoToTabWithId(u64),                            // u64 - tab_id
3453    CloseTabWithId(u64),                           // u64 - tab_id
3454    RenameTabWithId(u64, String),                  // u64 - tab_id, String - new name
3455    BreakPanesToTabWithId(Vec<PaneId>, u64, bool), // u64 - tab_id, bool -
3456    // should_change_focus_to_target_tab
3457    ReloadPlugin(u32), // u32 - plugin pane id
3458    LoadNewPlugin {
3459        url: String,
3460        config: BTreeMap<String, String>,
3461        load_in_background: bool,
3462        skip_plugin_cache: bool,
3463    },
3464    RebindKeys {
3465        keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
3466        keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
3467        write_config_to_disk: bool,
3468    },
3469    ListClients,
3470    ChangeHostFolder(PathBuf),
3471    SetFloatingPanePinned(PaneId, bool), // bool -> should be pinned
3472    StackPanes(Vec<PaneId>),
3473    ChangeFloatingPanesCoordinates(Vec<(PaneId, FloatingPaneCoordinates)>),
3474    TogglePaneBorderless(PaneId),
3475    SetPaneBorderless(PaneId, bool),
3476    OpenCommandPaneNearPlugin(CommandToRun, Context),
3477    OpenTerminalNearPlugin(FileToOpen),
3478    OpenTerminalFloatingNearPlugin(FileToOpen, Option<FloatingPaneCoordinates>),
3479    OpenTerminalInPlaceOfPlugin(FileToOpen, bool), // bool -> close_plugin_after_replace
3480    OpenCommandPaneFloatingNearPlugin(CommandToRun, Option<FloatingPaneCoordinates>, Context),
3481    OpenCommandPaneInPlaceOfPlugin(CommandToRun, bool, Context), // bool ->
3482    // close_plugin_after_replace
3483    OpenFileNearPlugin(FileToOpen, Context),
3484    OpenFileFloatingNearPlugin(FileToOpen, Option<FloatingPaneCoordinates>, Context),
3485    StartWebServer,
3486    StopWebServer,
3487    ShareCurrentSession,
3488    StopSharingCurrentSession,
3489    OpenFileInPlaceOfPlugin(FileToOpen, bool, Context), // bool -> close_plugin_after_replace
3490    GroupAndUngroupPanes(Vec<PaneId>, Vec<PaneId>, bool), // panes to group, panes to ungroup,
3491    // bool -> for all clients
3492    HighlightAndUnhighlightPanes(Vec<PaneId>, Vec<PaneId>), // panes to highlight, panes to
3493    // unhighlight
3494    CloseMultiplePanes(Vec<PaneId>),
3495    FloatMultiplePanes(Vec<PaneId>),
3496    EmbedMultiplePanes(Vec<PaneId>),
3497    QueryWebServerStatus,
3498    SetSelfMouseSelectionSupport(bool),
3499    GenerateWebLoginToken(Option<String>, bool), // (token_label, read_only)
3500    RevokeWebLoginToken(String), // String -> token id (provided name or generated id)
3501    ListWebLoginTokens,
3502    RevokeAllWebLoginTokens,
3503    RenameWebLoginToken(String, String), // (original_name, new_name)
3504    InterceptKeyPresses,
3505    ClearKeyPressesIntercepts,
3506    ReplacePaneWithExistingPane(PaneId, PaneId, bool), // (pane id to replace, pane id of existing,
3507    // suppress_replaced_pane)
3508    RunAction(Action, BTreeMap<String, String>),
3509    CopyToClipboard(String), // text to copy
3510    OverrideLayout(
3511        LayoutInfo,
3512        bool,                     // retain_existing_terminal_panes
3513        bool,                     // retain_existing_plugin_panes
3514        bool,                     // apply_only_to_active_tab,
3515        BTreeMap<String, String>, // context
3516    ),
3517    SaveLayout {
3518        layout_name: String,
3519        layout_kdl: String,
3520        overwrite: bool,
3521    },
3522    DeleteLayout {
3523        layout_name: String,
3524    },
3525    RenameLayout {
3526        old_layout_name: String,
3527        new_layout_name: String,
3528    },
3529    EditLayout {
3530        layout_name: String,
3531        context: Context,
3532    },
3533    GenerateRandomName,
3534    DumpLayout(String),
3535    ParseLayout(String), // String contains raw KDL layout
3536    GetLayoutDir,
3537    GetFocusedPaneInfo,
3538    SaveSession,
3539    CurrentSessionLastSavedTime,
3540    GetPaneInfo(PaneId),
3541    GetTabInfo(usize), // tab_id
3542    GetSessionEnvironmentVariables,
3543    OpenCommandPaneInNewTab(CommandToRun, Context),
3544    OpenPluginPaneInNewTab {
3545        plugin_url: String,
3546        configuration: BTreeMap<String, String>,
3547        context: Context,
3548    },
3549    OpenEditorPaneInNewTab(FileToOpen, Context),
3550    OpenCommandPaneInPlaceOfPaneId(PaneId, CommandToRun, bool, Context), // bool = close_replaced_pane
3551    OpenTerminalPaneInPlaceOfPaneId(PaneId, FileToOpen, bool),
3552    OpenEditPaneInPlaceOfPaneId(PaneId, FileToOpen, bool, Context),
3553    HideFloatingPanes {
3554        tab_id: Option<usize>,
3555    },
3556    ShowFloatingPanes {
3557        tab_id: Option<usize>,
3558    },
3559    SetPaneColor(PaneId, Option<String>, Option<String>), // (pane_id, fg, bg)
3560    SetPaneRegexHighlights(PaneId, Vec<RegexHighlight>),
3561    ClearPaneHighlights(PaneId),
3562    OpenPluginPaneFloating {
3563        plugin_url: String,
3564        configuration: BTreeMap<String, String>,
3565        floating_pane_coordinates: Option<FloatingPaneCoordinates>,
3566        context: BTreeMap<String, String>,
3567    },
3568    ListWindowsVolumes,
3569}
3570
3571// Response type for plugin API methods that open a pane in a new tab
3572#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3573pub struct OpenPaneInNewTabResponse {
3574    pub tab_id: Option<usize>,
3575    pub pane_id: Option<PaneId>,
3576}
3577
3578// Response types for plugin API methods that create tabs
3579pub type NewTabResponse = Option<usize>;
3580pub type NewTabsResponse = Vec<usize>;
3581pub type FocusOrCreateTabResponse = Option<usize>;
3582pub type BreakPanesToNewTabResponse = Option<usize>;
3583pub type BreakPanesToTabWithIndexResponse = Option<usize>;
3584pub type BreakPanesToTabWithIdResponse = Option<usize>;
3585
3586// Response types for plugin API methods that create panes
3587pub type OpenFileResponse = Option<PaneId>;
3588pub type OpenFileFloatingResponse = Option<PaneId>;
3589pub type OpenFileInPlaceResponse = Option<PaneId>;
3590pub type OpenFileNearPluginResponse = Option<PaneId>;
3591pub type OpenFileFloatingNearPluginResponse = Option<PaneId>;
3592pub type OpenFileInPlaceOfPluginResponse = Option<PaneId>;
3593
3594pub type OpenTerminalResponse = Option<PaneId>;
3595pub type OpenTerminalFloatingResponse = Option<PaneId>;
3596pub type OpenTerminalInPlaceResponse = Option<PaneId>;
3597pub type OpenTerminalNearPluginResponse = Option<PaneId>;
3598pub type OpenTerminalFloatingNearPluginResponse = Option<PaneId>;
3599pub type OpenTerminalInPlaceOfPluginResponse = Option<PaneId>;
3600
3601pub type OpenCommandPaneResponse = Option<PaneId>;
3602pub type OpenCommandPaneFloatingResponse = Option<PaneId>;
3603pub type OpenCommandPaneInPlaceResponse = Option<PaneId>;
3604pub type OpenCommandPaneNearPluginResponse = Option<PaneId>;
3605pub type OpenCommandPaneFloatingNearPluginResponse = Option<PaneId>;
3606pub type OpenCommandPaneInPlaceOfPluginResponse = Option<PaneId>;
3607pub type OpenCommandPaneBackgroundResponse = Option<PaneId>;
3608pub type OpenCommandPaneInPlaceOfPaneIdResponse = Option<PaneId>;
3609pub type OpenTerminalPaneInPlaceOfPaneIdResponse = Option<PaneId>;
3610pub type OpenEditPaneInPlaceOfPaneIdResponse = Option<PaneId>;
3611pub type OpenPluginPaneFloatingResponse = Option<PaneId>;