use crate::detect::{Row, Rows};
const SHELLS: &[&str] = &[
"bash", "zsh", "fish", "dash", "sh", "ksh", "tcsh", "csh", "nu", "elvish", "xonsh",
];
pub fn detect() -> Rows {
let Some(shell) = find_shell_ancestor().or_else(shell_from_env) else {
return Vec::new();
};
let value = match shell_version(&shell) {
Some(v) => format!("{shell} {v}"),
None => shell,
};
vec![Row::val(value)]
}
fn find_shell_ancestor() -> Option<String> {
let mut pid = crate::sys::ppid_comm(std::process::id())?.0;
let mut guard = 0u32;
while pid > 1 && guard < 64 {
let (next, comm) = crate::sys::ppid_comm(pid)?;
let name = comm.strip_prefix('-').unwrap_or(&comm);
if SHELLS.contains(&name) {
return Some(name.to_string());
}
if next == pid {
break; }
pid = next;
guard += 1;
}
None
}
fn shell_version(shell: &str) -> Option<String> {
let out = crate::util::cmd(shell, &["--version"])?;
let line = out.lines().next()?;
for token in line.split_whitespace() {
if token.starts_with(|c: char| c.is_ascii_digit()) {
let ver: String = token
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.')
.collect();
if !ver.is_empty() {
return Some(ver);
}
}
}
None
}
fn shell_from_env() -> Option<String> {
let sh = std::env::var("SHELL").ok()?;
let base = sh.rsplit('/').next()?;
if base.is_empty() {
None
} else {
Some(base.to_string())
}
}