use crate::rmux::Running;
use crate::session::Session;
use serde::Serialize;
const QUIET_SECS: u64 = 2;
#[derive(Debug, Serialize)]
pub struct Tab {
pub name: String,
pub label: String,
pub cwd: Option<String>,
pub state: &'static str,
pub color: Option<String>,
pub tab: Option<String>,
pub attached: bool,
pub session_id: Option<String>,
}
pub fn build(running: Vec<Running>, sessions: &[Session], now: u64) -> Vec<Tab> {
running
.into_iter()
.map(|r| {
let session_id = r.pid.and_then(|pid| {
sessions
.iter()
.find(|s| s.remote.is_none() && s.root_pid() == Some(pid))
.map(|s| s.session_id.clone())
});
Tab {
state: state_of(&r, now),
label: r.label.clone().unwrap_or_else(|| short_name(&r.name)),
cwd: r.cwd.as_ref().map(|p| p.to_string_lossy().into_owned()),
color: r.color.clone(),
tab: r.tab.clone(),
attached: r.attached,
session_id,
name: r.name,
}
})
.collect()
}
fn state_of(r: &Running, now: u64) -> &'static str {
if let Some(state) = r.state.filter(|s| s.is_current(now)) {
return match state.signal {
crate::hook::Signal::NeedsInput => "needs-input",
signal if signal.is_working() => "working",
_ => "idle",
};
}
match r.activity {
Some(at) if now.saturating_sub(at) <= QUIET_SECS => "working",
_ => "idle",
}
}
fn short_name(name: &str) -> String {
name.strip_prefix("cctop-").unwrap_or(name).to_string()
}
pub fn is_tab(running: &[Running], name: &str) -> bool {
running.iter().any(|r| r.name == name)
}
#[cfg(test)]
mod tests {
use super::*;
fn running(name: &str, pid: u32, activity: u64) -> Running {
Running {
name: name.into(),
pid: Some(pid),
cwd: None,
attached: false,
activity: Some(activity),
label: None,
profile: None,
order: None,
state: None,
color: None,
tab: None,
pane: None,
axis: None,
window: None,
}
}
#[test]
fn tabs_keep_their_order_and_find_their_sessions() {
let mut session = Session::new(crate::pricing::Provider::Claude, "abc".into());
session.process = Some(crate::proc::ProcInfo {
pids: 1,
cpu: 0.0,
memory: 0,
command: "claude".into(),
process_list: vec![crate::proc::ProcEntry {
pid: 42,
cpu: 0.0,
memory: 0,
args: "claude".into(),
is_root: true,
ghost: false,
}],
});
let now = 1_000;
let tabs = build(
vec![
running("cctop-b", 42, now - 1),
running("cctop-a", 7, now - 60),
],
&[session],
now,
);
assert_eq!(tabs[0].name, "cctop-b", "the bar's order is kept");
assert_eq!(tabs[0].label, "b");
assert_eq!(tabs[0].session_id.as_deref(), Some("abc"));
assert_eq!(tabs[0].state, "working");
assert_eq!(tabs[1].session_id, None);
assert_eq!(tabs[1].state, "idle");
}
#[test]
fn only_a_live_tab_can_be_opened() {
let live = vec![running("cctop-a", 1, 0)];
assert!(is_tab(&live, "cctop-a"));
assert!(!is_tab(&live, "work"), "an rmux session cctop did not open");
assert!(!is_tab(&live, "cctop-gone"));
}
}