Smart Neovim launcher for yyx990803/launch-editor
at main 91 lines 2.7 kB view raw
1use std::{ 2 cell::LazyCell, 3 ffi::{OsStr, OsString}, 4 os::unix::{ffi::OsStrExt as _, fs::FileTypeExt as _}, 5 path::{Path, PathBuf}, 6 process::Command, 7}; 8 9use anyhow::{Result, bail}; 10use log::{debug, trace}; 11use walkdir::WalkDir; 12 13#[derive(Debug)] 14pub struct Process { 15 pid: u64, 16 cwd: LazyCell<PathBuf, Box<dyn FnOnce() -> PathBuf>>, 17} 18 19impl Process { 20 pub fn from_pid(pid: u64) -> Self { 21 Self { 22 pid, 23 cwd: LazyCell::new(Box::new(move || { 24 trace!("Running `lsof` to find working directory of pid={pid}"); 25 Command::new("lsof") 26 .args(["-a", "-d", "cwd", "-Fn", "-p", &pid.to_string()]) 27 .output() 28 .unwrap() 29 .stdout 30 .split(|c| *c == b'\n') 31 .find_map(|l| { 32 l.strip_prefix(b"n") 33 .map(OsStr::from_bytes) 34 .map(PathBuf::from) 35 }) 36 .expect("failed to parse lsof output: missing cwd") 37 })), 38 } 39 } 40 41 pub fn pid(&self) -> u64 { 42 self.pid 43 } 44 45 pub fn current_dir(&self) -> &Path { 46 self.cwd.as_path() 47 } 48} 49 50pub fn nvim_sockets() -> Result<impl Iterator<Item = (Process, OsString)>> { 51 // ask a temporary, headless nvim, where it puts its sockets 52 let run_dir = Command::new("nvim") 53 .args([ 54 "--headless", 55 "-c", 56 "lua print(vim.fn.stdpath('run'))", 57 "-c", 58 "q", 59 ]) 60 .output()? 61 .stderr; 62 let run_dir = Path::new(OsStr::from_bytes(&run_dir)); 63 // move to the parent, where all "run" directories will be present 64 let Some(run_dir_parent) = run_dir.parent() else { 65 bail!("Couldn't figure out Neovim's 'run' directory location"); 66 }; 67 68 Ok(WalkDir::new(run_dir_parent) 69 .min_depth(2) 70 .into_iter() 71 .filter_map(|entry| entry.inspect_err(|e| debug!("{e}")).ok()) 72 .filter_map(|entry| { 73 trace!("Checking {}", entry.path().display()); 74 75 let name = entry.file_name().to_str()?; 76 let mut name_parts = name.split('.'); 77 let Some("nvim") = name_parts.next() else { 78 return None; 79 }; 80 let pid = name_parts.next()?; 81 let pid = pid.parse().ok()?; 82 83 entry 84 .metadata() 85 .inspect_err(|e| debug!("{e}")) 86 .ok()? 87 .file_type() 88 .is_socket() 89 .then(|| (Process::from_pid(pid), entry.path().as_os_str().to_owned())) 90 })) 91}