use std::collections::HashMap;
use std::fs;
use std::process::Command;
use tracing::{debug, trace};
pub fn find_pane_for_pid(pid: u32) -> Option<String> {
let pane_pids = list_tmux_panes()?;
if pane_pids.is_empty() {
debug!("No tmux panes found");
return None;
}
trace!(pane_count = pane_pids.len(), "Found tmux panes");
let mut current_pid = pid;
let mut depth = 0;
const MAX_DEPTH: u32 = 20;
while depth < MAX_DEPTH {
if let Some(pane_id) = pane_pids.get(¤t_pid) {
debug!(pid, pane_id, depth, "Found tmux pane for process");
return Some(pane_id.clone());
}
match get_parent_pid(current_pid) {
Some(ppid) if ppid > 1 => {
current_pid = ppid;
depth += 1;
}
_ => {
break;
}
}
}
debug!(pid, "No tmux pane found for process");
None
}
fn list_tmux_panes() -> Option<HashMap<u32, String>> {
let output = Command::new("tmux")
.args(["list-panes", "-a", "-F", "#{pane_id} #{pane_pid}"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut pane_pids = HashMap::new();
for line in stdout.lines() {
let mut parts = line.split_whitespace();
if let (Some(pane_id), Some(pane_pid_str)) = (parts.next(), parts.next()) {
if let Ok(pane_pid) = pane_pid_str.parse::<u32>() {
pane_pids.insert(pane_pid, pane_id.to_string());
}
}
}
Some(pane_pids)
}
pub(crate) fn get_parent_pid(pid: u32) -> Option<u32> {
let stat_path = format!("/proc/{pid}/stat");
let stat_content = fs::read_to_string(&stat_path).ok()?;
let close_paren = stat_content.rfind(')')?;
let after_comm = stat_content.get(close_paren + 1..)?;
let fields: Vec<&str> = after_comm.split_whitespace().collect();
fields.get(1)?.parse().ok()
}
pub(crate) fn get_process_start_time(pid: u32) -> Option<u64> {
let stat_path = format!("/proc/{pid}/stat");
let stat_content = fs::read_to_string(&stat_path).ok()?;
let close_paren = stat_content.rfind(')')?;
let after_comm = stat_content.get(close_paren + 1..)?;
let fields: Vec<&str> = after_comm.split_whitespace().collect();
fields.get(19)?.parse().ok()
}
pub fn is_tmux_available() -> bool {
Command::new("tmux")
.arg("-V")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_parent_pid_self() {
let pid = std::process::id();
let ppid = get_parent_pid(pid);
assert!(ppid.is_some());
assert!(ppid.unwrap() > 0);
}
#[test]
fn test_get_parent_pid_nonexistent() {
let ppid = get_parent_pid(999_999_999);
assert!(ppid.is_none());
}
#[test]
fn test_is_tmux_available() {
let _ = is_tmux_available();
}
}