use std::io::IsTerminal;
use super::tmux_query::{Pane, Snapshot, Window, poll};
pub(crate) fn run(socket: &str) -> i32 {
let snap = poll(socket);
if let Some(e) = &snap.error {
eprintln!("ztmux tree: {e}");
return 1;
}
print!("{}", render(&snap, std::io::stdout().is_terminal()));
0
}
fn render(snap: &Snapshot, color: bool) -> String {
let paint = |s: &str, code: &str| -> String {
if color {
format!("\x1b[{code}m{s}\x1b[0m")
} else {
s.to_string()
}
};
let mut out = String::new();
for s in &snap.sessions {
let marker = if s.attached {
paint(" *", "35")
} else {
String::new()
};
out.push_str(&format!(
"{} {} {}{}\n",
paint("●", "36"),
paint(&s.name, "1;36"),
paint(&format!("({} win)", s.windows), "2"),
marker,
));
let mut wins: Vec<&Window> = snap
.windows
.iter()
.filter(|w| w.session == s.name)
.collect();
wins.sort_by_key(|w| w.index);
for (wi, w) in wins.iter().enumerate() {
let last_win = wi + 1 == wins.len();
let wbr = if last_win { "└─" } else { "├─" };
let act = if w.active {
paint(" ●", "32")
} else {
String::new()
};
out.push_str(&format!(
"{} {}: {} {}{}\n",
wbr,
w.index,
paint(&w.name, "32"),
paint(&format!("[{} panes]", w.panes), "2"),
act,
));
let mut panes: Vec<&Pane> = snap
.panes
.iter()
.filter(|p| p.session == w.session && p.window == w.index)
.collect();
panes.sort_by_key(|p| p.index);
let cont = if last_win { " " } else { "│ " };
for (pi, p) in panes.iter().enumerate() {
let last_pane = pi + 1 == panes.len();
let pbr = if last_pane { "└─" } else { "├─" };
let flag = if p.active {
paint(" ●", "32")
} else if p.dead {
paint(" ✝", "31")
} else {
String::new()
};
out.push_str(&format!(
"{}{} {} {}{}\n",
cont,
pbr,
p.id,
paint(&p.command, "33"),
flag,
));
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::super::tmux_query::{Pane, Session, Window};
use super::*;
fn snap() -> Snapshot {
Snapshot {
sessions: vec![
Session {
name: "work".into(),
windows: 2,
attached: true,
..Default::default()
},
Session {
name: "scratch".into(),
windows: 1,
..Default::default()
},
],
windows: vec![
Window {
session: "work".into(),
index: 0,
name: "zsh".into(),
panes: 2,
..Default::default()
},
Window {
session: "work".into(),
index: 1,
name: "editor".into(),
active: true,
panes: 1,
..Default::default()
},
Window {
session: "scratch".into(),
index: 0,
name: "zsh".into(),
panes: 1,
..Default::default()
},
],
panes: vec![
Pane {
session: "work".into(),
window: 0,
index: 0,
id: "%0".into(),
command: "zsh".into(),
..Default::default()
},
Pane {
session: "work".into(),
window: 0,
index: 1,
id: "%1".into(),
command: "vim".into(),
..Default::default()
},
Pane {
session: "work".into(),
window: 1,
index: 0,
id: "%2".into(),
command: "nvim".into(),
active: true,
..Default::default()
},
Pane {
session: "scratch".into(),
window: 0,
index: 0,
id: "%3".into(),
command: "htop".into(),
..Default::default()
},
],
clients: vec![],
error: None,
}
}
#[test]
fn renders_a_plain_box_drawing_tree() {
assert_eq!(
render(&snap(), false),
"\
● work (2 win) *
├─ 0: zsh [2 panes]
│ ├─ %0 zsh
│ └─ %1 vim
└─ 1: editor [1 panes] ●
└─ %2 nvim ●
● scratch (1 win)
└─ 0: zsh [1 panes]
└─ %3 htop
"
);
}
#[test]
fn color_wraps_in_ansi_escapes() {
let c = render(&snap(), true);
assert!(c.contains("\x1b["));
assert!(c.contains("\x1b[0m"));
assert!(c.contains("work") && c.contains("nvim") && c.contains("%3"));
}
}