1use std::env::{current_dir, temp_dir};2use std::path::Path;3use std::time::Duration;4use std::{fs, io};56use anyhow::{bail, Context as _};7use nix::libc;8use remowt_link_shared::editor::EditorEndpointsClient;9use tokio::process::Command;10use zbus::{fdo, interface, proxy, Connection};1112use remowt_link_shared::BifConfig;1314const BUS_NAME: &str = "lach.RemowtEditor";15const SERVICE_PATH: &str = "/lach/Editor";1617pub struct EditorService {18 editor: EditorEndpointsClient<BifConfig>,19}2021#[interface(name = "lach.RemowtEditor")]22impl EditorService {23 24 25 async fn edit(&self, socket_path: String) -> fdo::Result<()> {26 self.editor27 .open_editor(socket_path)28 .await29 .map_err(|e| fdo::Error::Failed(format!("requesting editor on the User: {e}")))?30 .map_err(|e| fdo::Error::Failed(format!("editor failed: {e}")))?;31 Ok(())32 }33}3435pub async fn serve(36 conn: &Connection,37 editor: EditorEndpointsClient<BifConfig>,38) -> anyhow::Result<()> {39 conn.object_server()40 .at(SERVICE_PATH, EditorService { editor })41 .await?;42 conn.request_name(BUS_NAME).await?;43 Ok(())44}4546#[proxy(interface = "lach.RemowtEditor")]47trait RemowtEditor {48 async fn edit(&self, socket_path: &str) -> fdo::Result<()>;49}5051pub async fn edit(path: String) -> anyhow::Result<()> {52 let path = Path::new(&path);53 let abs = if path.is_absolute() {54 path.to_path_buf()55 } else {56 current_dir()?.join(path)57 };5859 let sock = temp_dir().join(format!("remowt-nvim-{}.sock", uuid::Uuid::new_v4()));60 let sock_str = sock61 .to_str()62 .context("temp socket path is not utf-8")?63 .to_owned();6465 let mut child = Command::new("nvim");66 child67 .arg("--headless")68 .arg("--listen")69 .arg(&sock)70 .arg("--")71 .arg(&abs)72 .kill_on_drop(true);73 74 unsafe {75 child.pre_exec(|| {76 if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL as libc::c_ulong) != 0 {77 return Err(io::Error::last_os_error());78 }79 Ok(())80 });81 }82 let mut child = child.spawn().context("spawning nvim")?;8384 wait_for_socket(&sock)85 .await86 .context("nvim did not start its server")?;8788 let conn = Connection::session()89 .await90 .context("connecting to the session bus (DBUS_SESSION_BUS_ADDRESS)")?;91 let proxy = RemowtEditorProxy::builder(&conn)92 .destination(BUS_NAME)?93 .path(SERVICE_PATH)?94 .build()95 .await?;96 let result = proxy.edit(&sock_str).await;9798 if tokio::time::timeout(Duration::from_secs(2), child.wait())99 .await100 .is_err()101 {102 let _ = child.kill().await;103 }104 let _ = fs::remove_file(&sock);105106 result?;107 Ok(())108}109110111async fn wait_for_socket(path: &Path) -> anyhow::Result<()> {112 for _ in 0..200 {113 if tokio::fs::try_exists(path).await.unwrap_or(false) {114 return Ok(());115 }116 tokio::time::sleep(Duration::from_millis(50)).await;117 }118 bail!("timed out waiting for {}", path.display())119}