Skip to main content

rz_cli/
status.rs

1//! Pane status summary for the `status` subcommand.
2
3use crate::protocol::SENTINEL;
4use crate::zellij::PaneInfo;
5
6/// Per-pane status line.
7pub struct PaneStatus {
8    pub pane_id: String,
9    pub title: String,
10    pub command: String,
11    pub running: bool,
12    pub exit_status: Option<i32>,
13    pub message_count: usize,
14}
15
16/// Summary of all panes.
17pub struct StatusSummary {
18    pub total: usize,
19    pub running: usize,
20    pub exited: usize,
21    pub panes: Vec<PaneStatus>,
22}
23
24/// Count `@@RZ:` lines in a scrollback string.
25fn count_messages(scrollback: &str) -> usize {
26    scrollback.lines().filter(|l| l.contains(SENTINEL)).count()
27}
28
29/// Build a [`StatusSummary`] from a list of panes and a function that provides
30/// each pane's scrollback.
31///
32/// The caller supplies `get_scrollback` so the function stays testable without
33/// hitting real Zellij — in production, pass `|id| rz::zellij::dump(id)`.
34pub fn summarize(
35    panes: &[PaneInfo],
36    get_scrollback: impl Fn(&str) -> Option<String>,
37) -> StatusSummary {
38    let mut running = 0usize;
39    let mut exited = 0usize;
40    let mut statuses = Vec::with_capacity(panes.len());
41
42    for pane in panes {
43        if pane.exited {
44            exited += 1;
45        } else {
46            running += 1;
47        }
48
49        let pane_id = pane.pane_id();
50        let msg_count = get_scrollback(&pane_id)
51            .map(|s| count_messages(&s))
52            .unwrap_or(0);
53
54        let command = pane
55            .pane_command
56            .as_deref()
57            .map(|c| {
58                // basename only
59                c.rsplit('/').next().unwrap_or(c)
60            })
61            .unwrap_or("-")
62            .to_string();
63
64        statuses.push(PaneStatus {
65            pane_id,
66            title: pane.title.clone(),
67            command,
68            running: !pane.exited,
69            exit_status: pane.exit_status,
70            message_count: msg_count,
71        });
72    }
73
74    StatusSummary {
75        total: panes.len(),
76        running,
77        exited,
78        panes: statuses,
79    }
80}
81
82/// Format the summary as a human-readable string.
83pub fn format_summary(summary: &StatusSummary) -> String {
84    let mut out = format!(
85        "{} panes ({} running, {} exited)\n",
86        summary.total, summary.running, summary.exited,
87    );
88
89    for p in &summary.panes {
90        let state = if p.running {
91            "running".to_string()
92        } else {
93            match p.exit_status {
94                Some(code) => format!("exited ({})", code),
95                None => "exited".to_string(),
96            }
97        };
98        out.push_str(&format!(
99            "  {} | {} | {} | {} | {} msgs\n",
100            p.pane_id, p.title, p.command, state, p.message_count,
101        ));
102    }
103
104    out
105}