refactor: consolidate nvim crates, extract server library, and update workspace dependencies

This commit is contained in:
Riz Ashraf committed 2026-10-04 01:42:59 +01:00
1 parent a083719cf1
commit 533adfd41b
53 files changed
+5967 -1230

No files matched your search

+85 -2
View File
@@ -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(&params);
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])"));
}
}