Smart Neovim launcher for yyx990803/launch-editor
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}