feat(nvim): implement auto-spawning headless instance and interactive preemption
This commit is contained in:
1 parent
7d783d2f37
commit
914cecbaf2
2 files changed
+95
-8
No files matched your search
@@ -0,0 +1,42 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
pub static ACTIVE_SOCKET: LazyLock<Arc<std::sync::Mutex<Option<String>>>> =
|
||||||
|
LazyLock::new(|| Arc::new(std::sync::Mutex::new(None)));
|
||||||
|
|
||||||
|
pub static HEADLESS_PROC: LazyLock<Arc<std::sync::Mutex<Option<tokio::process::Child>>>> =
|
||||||
|
LazyLock::new(|| Arc::new(std::sync::Mutex::new(None)));
|
||||||
|
|
||||||
|
pub async fn spawn_headless_nvim() -> Result<String, String> {
|
||||||
|
if std::process::Command::new("nvim").arg("--version").output().is_err() {
|
||||||
|
return Err("Error: Neovim is not installed or not in PATH. Cannot execute nvim tool. Fallback to basic terminal tools.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let socket_name = format!(r"\\.\pipe\agy-headless-nvim-{}", std::process::id());
|
||||||
|
#[cfg(unix)]
|
||||||
|
let socket_name = format!("/tmp/agy-headless-nvim-{}.sock", std::process::id());
|
||||||
|
|
||||||
|
let child = tokio::process::Command::new("nvim")
|
||||||
|
.arg("--headless")
|
||||||
|
.arg("--listen")
|
||||||
|
.arg(&socket_name)
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("Failed to spawn headless Neovim: {}", e))?;
|
||||||
|
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
|
||||||
|
|
||||||
|
let mut old_child = {
|
||||||
|
let mut lock = HEADLESS_PROC.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
let old = lock.take();
|
||||||
|
*lock = Some(child);
|
||||||
|
old
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut old) = old_child {
|
||||||
|
let _ = old.kill().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(socket_name)
|
||||||
|
}
|
||||||
+53
-8
@@ -1,3 +1,5 @@
|
|||||||
|
mod headless;
|
||||||
|
use headless::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
@@ -160,17 +162,55 @@ fn handle_nvim_notification(params: &[rmpv::Value]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
|
async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
|
||||||
|
let interactive_socket = get_socket_path().await.ok();
|
||||||
|
let mut should_kill_headless = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
let conn_lock = NVIM_CONN.lock().unwrap_or_else(|e| e.into_inner());
|
let mut conn_lock = NVIM_CONN.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
if let Some(sender) = conn_lock.as_ref()
|
if let Some(sender) = conn_lock.as_ref() {
|
||||||
&& !sender.is_closed()
|
if !sender.is_closed() {
|
||||||
{
|
let current_socket = {
|
||||||
return Ok(sender.clone());
|
let active_lock = ACTIVE_SOCKET.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
active_lock.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref interactive) = interactive_socket {
|
||||||
|
if Some(interactive.clone()) != current_socket {
|
||||||
|
tracing::info!("Interactive Preemption: New Neovim instance detected at {}. Disconnecting from current.", interactive);
|
||||||
|
should_kill_headless = true;
|
||||||
|
*conn_lock = None;
|
||||||
|
{
|
||||||
|
let mut active_lock2 = ACTIVE_SOCKET.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
*active_lock2 = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(sender.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(sender.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Establishing new persistent connection to Neovim");
|
if should_kill_headless {
|
||||||
let socket_path = get_socket_path().await?;
|
let mut child_to_kill = {
|
||||||
|
let mut proc_lock = HEADLESS_PROC.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
proc_lock.take()
|
||||||
|
};
|
||||||
|
if let Some(mut child) = child_to_kill {
|
||||||
|
let _ = child.kill().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let socket_path = if let Some(p) = interactive_socket {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
tracing::info!("No interactive Neovim detected. Spawning headless instance.");
|
||||||
|
spawn_headless_nvim().await?
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("Establishing new persistent connection to Neovim at {}", socket_path);
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let stream = {
|
let stream = {
|
||||||
@@ -183,11 +223,16 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let stream = {
|
let stream = {
|
||||||
use tokio::net::UnixStream;
|
use tokio::net::UnixStream;
|
||||||
UnixStream::connect(socket_path)
|
UnixStream::connect(&socket_path)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut active_lock = ACTIVE_SOCKET.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
*active_lock = Some(socket_path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
let (mut read_half, mut write_half) = tokio::io::split(stream);
|
let (mut read_half, mut write_half) = tokio::io::split(stream);
|
||||||
let (tx, mut rx) = mpsc::channel::<NvimRequest>(32);
|
let (tx, mut rx) = mpsc::channel::<NvimRequest>(32);
|
||||||
type PendingRequestsMap =
|
type PendingRequestsMap =
|
||||||
|
|||||||
Reference in new issue
Block a user