use serde::{Deserialize, Serialize};
use crate::id::PaneId;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TearPane {
pub id: PaneId,
pub shell: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub cwd: Option<String>,
#[serde(default)]
pub env: Vec<(String, String)>,
pub size_cells: (u16, u16),
pub origin_cells: (u16, u16),
pub state: PaneState,
#[serde(default)]
pub title: String,
#[serde(default)]
pub input_policy: InputPolicy,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum InputPolicy {
Free,
Locked,
Leader { id: u64 },
}
impl Default for InputPolicy {
fn default() -> Self {
Self::Free
}
}
impl InputPolicy {
#[must_use]
pub fn label(&self) -> &'static str {
match self {
InputPolicy::Free => "free",
InputPolicy::Locked => "locked",
InputPolicy::Leader { .. } => "leader",
}
}
#[must_use]
pub fn leader(id: u64) -> Self {
InputPolicy::Leader { id }
}
#[must_use]
pub fn leader_id(&self) -> Option<u64> {
match self {
InputPolicy::Leader { id } => Some(*id),
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PaneState {
Running,
Exited { code: i32 },
Spawning,
}
impl Default for PaneState {
fn default() -> Self {
Self::Spawning
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct PaneStats {
pub bytes_consumed: u64,
pub scrollback_lines: u32,
pub seconds_since_last_byte: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pane_state_default_is_spawning() {
assert_eq!(PaneState::default(), PaneState::Spawning);
}
#[test]
fn input_policy_leader_serialises_as_kind_tag() {
let p = InputPolicy::Leader { id: 42 };
let s = serde_json::to_string(&p).unwrap();
assert!(s.contains("\"kind\":\"leader\""), "got: {s}");
assert!(s.contains("42"), "got: {s}");
}
#[test]
fn input_policy_label_covers_every_variant() {
assert_eq!(InputPolicy::Free.label(), "free");
assert_eq!(InputPolicy::Locked.label(), "locked");
assert_eq!(InputPolicy::leader(7).label(), "leader");
}
#[test]
fn input_policy_leader_constructor_matches_struct_form() {
assert_eq!(InputPolicy::leader(99), InputPolicy::Leader { id: 99 });
}
#[test]
fn input_policy_leader_id_returns_some_for_leader_and_none_otherwise() {
assert_eq!(InputPolicy::leader(5).leader_id(), Some(5));
assert_eq!(InputPolicy::Free.leader_id(), None);
assert_eq!(InputPolicy::Locked.leader_id(), None);
}
#[test]
fn input_policy_serde_round_trips_every_variant() {
for p in [
InputPolicy::Free,
InputPolicy::Locked,
InputPolicy::leader(42),
] {
let json = serde_json::to_string(&p).unwrap();
let back: InputPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(p, back, "round-trip failed for {p:?}");
}
}
#[test]
fn pane_default_fields_are_constructible() {
let p = TearPane {
id: PaneId(42),
shell: "/bin/zsh".into(),
args: vec![],
cwd: Some("/tmp".into()),
env: vec![],
size_cells: (120, 40),
origin_cells: (0, 0),
state: PaneState::Running,
title: "zsh".into(),
input_policy: InputPolicy::default(),
};
assert_eq!(p.state, PaneState::Running);
assert_eq!(p.size_cells, (120, 40));
assert_eq!(p.input_policy, InputPolicy::Free);
}
#[test]
fn input_policy_default_is_free() {
assert_eq!(InputPolicy::default(), InputPolicy::Free);
}
}