1use std::env::{current_dir, temp_dir};2use std::path::Path;3use std::time::Duration;4use std::{fs, io};56use anyhow::{anyhow, bail, Context as _};7use bifrostlink::declarative::RemoteEndpoints as _;8use nix::libc;9use remowt_link_shared::editor::EditorEndpointsClient;10use remowt_link_shared::{gateway, Address, BifConfig};11use tokio::process::Command;1213pub async fn forward(udp: bool, addr: String) -> anyhow::Result<()> {14 let rpc = gateway::connect(&gateway::socket_path()?).await?;15 let editor = EditorEndpointsClient::<BifConfig>::wrap(rpc.remote(Address::User));16 let local = if udp {17 editor.expose_udp(addr).await18 } else {19 editor.expose_tcp(addr).await20 }21 .map_err(|e| anyhow!("requesting forward on the User: {e}"))?22 .map_err(|e| anyhow!("forward failed: {e}"))?;23 println!("{local}");24 Ok(())25}2627pub async fn edit(path: String) -> anyhow::Result<()> {28 let path = Path::new(&path);29 let abs = if path.is_absolute() {30 path.to_path_buf()31 } else {32 current_dir()?.join(path)33 };3435 let sock = temp_dir().join(format!("remowt-nvim-{}.sock", uuid::Uuid::new_v4()));36 let sock_str = sock37 .to_str()38 .context("temp socket path is not utf-8")?39 .to_owned();4041 let mut child = Command::new("nvim");42 child43 .arg("--headless")44 .arg("--listen")45 .arg(&sock)46 .arg("--")47 .arg(&abs)48 .kill_on_drop(true);49 50 unsafe {51 child.pre_exec(|| {52 if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL as libc::c_ulong) != 0 {53 return Err(io::Error::last_os_error());54 }55 Ok(())56 });57 }58 let mut child = child.spawn().context("spawning nvim")?;5960 wait_for_socket(&sock)61 .await62 .context("nvim did not start its server")?;6364 let rpc = gateway::connect(&gateway::socket_path()?).await?;65 let editor = EditorEndpointsClient::<BifConfig>::wrap(rpc.remote(Address::User));66 let result = editor67 .open_editor(sock_str)68 .await69 .map_err(|e| anyhow!("requesting editor on the User: {e}"))70 .and_then(|r| r.map_err(|e| anyhow!("editor failed: {e}")));7172 if tokio::time::timeout(Duration::from_secs(2), child.wait())73 .await74 .is_err()75 {76 let _ = child.kill().await;77 }78 let _ = fs::remove_file(&sock);7980 result81}8283async fn wait_for_socket(path: &Path) -> anyhow::Result<()> {84 for _ in 0..200 {85 if tokio::fs::try_exists(path).await.unwrap_or(false) {86 return Ok(());87 }88 tokio::time::sleep(Duration::from_millis(50)).await;89 }90 bail!("timed out waiting for {}", path.display())91}