refactor: consolidate nvim crates, extract server library, and update workspace dependencies
This commit is contained in:
1 parent
a083719cf1
commit
533adfd41b
53 files changed
+5967
-1230
No files matched your search
@@ -57,3 +57,18 @@ pub async fn kill_headless_nvim() {
|
||||
let _ = child.kill().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_headless_nvim_lifecycle() {
|
||||
let res = spawn_headless_nvim().await;
|
||||
if let Ok(socket_name) = res {
|
||||
assert!(socket_name.contains("agy-headless-nvim"));
|
||||
kill_headless_nvim().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Antigravity Neovim MCP Instructions
|
||||
|
||||
When connected to this Neovim MCP server (win-nvim or linux-nvim), you have powerful tools to interact directly with the active Neovim editor.
|
||||
When connected to this Neovim MCP server (`win-nvim`), you have powerful tools to interact directly with the active Neovim editor.
|
||||
|
||||
## The Tool Arsenal
|
||||
The following tools are available:
|
||||
|
||||
+85
-2
@@ -34,9 +34,11 @@ pub async fn send_response(response: JsonRpcResponse) {
|
||||
// The MCP StdioTransport MUST use Newline-Delimited JSON (NDJSON).
|
||||
// Do NOT use LSP-style HTTP headers (e.g. Content-Length).
|
||||
// See MCP protocol spec (SEP-2575) and mcp-go-sdk bufio.Scanner implementation.
|
||||
let mut payload = Vec::with_capacity(msg.len() + 1);
|
||||
payload.extend_from_slice(msg.as_bytes());
|
||||
payload.push(b'\n');
|
||||
let mut stdout = tokio::io::stdout();
|
||||
let _ = stdout.write_all(msg.as_bytes()).await;
|
||||
let _ = stdout.write_all(b"\n").await;
|
||||
let _ = stdout.write_all(&payload).await;
|
||||
let _ = stdout.flush().await;
|
||||
}
|
||||
|
||||
@@ -169,6 +171,8 @@ pub struct NvimRequest {
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static NEXT_MSGID: AtomicU64 = AtomicU64::new(1);
|
||||
static RPC_SEMAPHORE: LazyLock<Arc<tokio::sync::Semaphore>> =
|
||||
LazyLock::new(|| Arc::new(tokio::sync::Semaphore::new(100)));
|
||||
static NVIM_CONN: LazyLock<Arc<std::sync::Mutex<Option<mpsc::Sender<NvimRequest>>>>> =
|
||||
LazyLock::new(|| Arc::new(std::sync::Mutex::new(None)));
|
||||
|
||||
@@ -776,7 +780,13 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
|
||||
let app_name = app_name.to_string();
|
||||
let app_version = app_version.to_string();
|
||||
|
||||
let permit = RPC_SEMAPHORE.clone().acquire_owned().await;
|
||||
if permit.is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
let id = msg.id.unwrap_or(json!(null));
|
||||
|
||||
match msg.method.as_str() {
|
||||
@@ -1759,4 +1769,77 @@ mod tests {
|
||||
let req = mcp_stdio::read_mcp_message(&mut reader).await;
|
||||
assert!(req.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nvim_state_and_notification() {
|
||||
let params = vec![
|
||||
rmpv::Value::String("CursorMoved".into()),
|
||||
rmpv::Value::Array(vec![
|
||||
rmpv::Value::Nil,
|
||||
rmpv::Value::Integer(15.into()),
|
||||
rmpv::Value::Integer(4.into()),
|
||||
rmpv::Value::Nil,
|
||||
]),
|
||||
];
|
||||
handle_nvim_notification(¶ms);
|
||||
let state = NVIM_STATE.lock().unwrap();
|
||||
assert_eq!(state.cursor, "Line: 15, Column: 4");
|
||||
}
|
||||
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_jsonrpc_structs_and_responses() {
|
||||
let req = JsonRpcRequest {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
id: Some(json!(1)),
|
||||
method: "nvim_get_cursor".to_string(),
|
||||
params: None,
|
||||
};
|
||||
let serialized = serde_json::to_string(&req).unwrap();
|
||||
assert!(serialized.contains("nvim_get_cursor"));
|
||||
|
||||
let resp = JsonRpcResponse {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
id: json!(1),
|
||||
result: Some(json!([1, 0])),
|
||||
error: None,
|
||||
};
|
||||
let resp_str = serde_json::to_string(&resp).unwrap();
|
||||
assert!(resp_str.contains("result"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_nvim_socket_and_interactive_helpers() {
|
||||
let is_int = is_interactive("agy-headless-nvim-1234").await;
|
||||
assert!(!is_int);
|
||||
|
||||
let path = get_socket_path().await;
|
||||
assert!(path.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rmpv_to_json_conversions() {
|
||||
assert_eq!(rmpv_to_json(&rmpv::Value::Nil), serde_json::Value::Null);
|
||||
assert_eq!(rmpv_to_json(&rmpv::Value::Boolean(true)), serde_json::json!(true));
|
||||
assert_eq!(rmpv_to_json(&rmpv::Value::Integer(42.into())), serde_json::json!(42));
|
||||
assert_eq!(rmpv_to_json(&rmpv::Value::F64(3.14)), serde_json::json!(3.14));
|
||||
assert_eq!(rmpv_to_json(&rmpv::Value::String("hello".into())), serde_json::json!("hello"));
|
||||
assert_eq!(rmpv_to_json(&rmpv::Value::Binary(vec![65, 66])), serde_json::json!("Binary([65, 66])"));
|
||||
|
||||
assert_eq!(
|
||||
rmpv_to_json(&rmpv::Value::Array(vec![rmpv::Value::Boolean(false)])),
|
||||
serde_json::json!([false])
|
||||
);
|
||||
let map_val = rmpv::Value::Map(vec![(
|
||||
rmpv::Value::String("key".into()),
|
||||
rmpv::Value::String("val".into()),
|
||||
)]);
|
||||
assert_eq!(rmpv_to_json(&map_val), serde_json::json!({"key": "val"}));
|
||||
let ext_val = rmpv::Value::Ext(1, vec![10, 20]);
|
||||
assert_eq!(rmpv_to_json(&ext_val), serde_json::json!("Ext(1, [10, 20])"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user