use std::{ cell::LazyCell, ffi::{OsStr, OsString}, os::unix::{ffi::OsStrExt as _, fs::FileTypeExt as _}, path::{Path, PathBuf}, process::Command, }; use anyhow::{Result, bail}; use log::{debug, trace}; use walkdir::WalkDir; #[derive(Debug)] pub struct Process { pid: u64, cwd: LazyCell PathBuf>>, } impl Process { pub fn from_pid(pid: u64) -> Self { Self { pid, cwd: LazyCell::new(Box::new(move || { trace!("Running `lsof` to find working directory of pid={pid}"); Command::new("lsof") .args(["-a", "-d", "cwd", "-Fn", "-p", &pid.to_string()]) .output() .unwrap() .stdout .split(|c| *c == b'\n') .find_map(|l| { l.strip_prefix(b"n") .map(OsStr::from_bytes) .map(PathBuf::from) }) .expect("failed to parse lsof output: missing cwd") })), } } pub fn pid(&self) -> u64 { self.pid } pub fn current_dir(&self) -> &Path { self.cwd.as_path() } } pub fn nvim_sockets() -> Result> { // ask a temporary, headless nvim, where it puts its sockets let run_dir = Command::new("nvim") .args([ "--headless", "-c", "lua print(vim.fn.stdpath('run'))", "-c", "q", ]) .output()? .stderr; let run_dir = Path::new(OsStr::from_bytes(&run_dir)); // move to the parent, where all "run" directories will be present let Some(run_dir_parent) = run_dir.parent() else { bail!("Couldn't figure out Neovim's 'run' directory location"); }; Ok(WalkDir::new(run_dir_parent) .min_depth(2) .into_iter() .filter_map(|entry| entry.inspect_err(|e| debug!("{e}")).ok()) .filter_map(|entry| { trace!("Checking {}", entry.path().display()); let name = entry.file_name().to_str()?; let mut name_parts = name.split('.'); let Some("nvim") = name_parts.next() else { return None; }; let pid = name_parts.next()?; let pid = pid.parse().ok()?; entry .metadata() .inspect_err(|e| debug!("{e}")) .ok()? .file_type() .is_socket() .then(|| (Process::from_pid(pid), entry.path().as_os_str().to_owned())) })) }