fix(proxy): fully non-blocking mcp-memory-stub proxy
This makes the stub purely non-blocking via tokio::spawn and fixes cross-OS compilation boundaries in build.cmd
This commit is contained in:
1 parent
67b6a0407e
commit
1fd1d119e6
6 files changed
+613
-677
No files matched your search
@@ -2,14 +2,14 @@
|
|||||||
setlocal
|
setlocal
|
||||||
|
|
||||||
echo Building mcp-memory (server and stub) for Windows...
|
echo Building mcp-memory (server and stub) for Windows...
|
||||||
rustup run stable cargo build --release
|
rustup run stable cargo build --release --workspace --exclude mcp-memory-linux-nvim
|
||||||
if %ERRORLEVEL% NEQ 0 (
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
echo Windows build failed!
|
echo Windows build failed!
|
||||||
exit /b %ERRORLEVEL%
|
exit /b %ERRORLEVEL%
|
||||||
)
|
)
|
||||||
|
|
||||||
echo Building mcp-memory (server and stub) for Linux (WSL)...
|
echo Building mcp-memory (server and stub) for Linux (WSL)...
|
||||||
rustup run stable cargo zigbuild --release --target x86_64-unknown-linux-musl
|
rustup run stable cargo zigbuild --release --workspace --exclude mcp-memory-win-nvim --target x86_64-unknown-linux-musl
|
||||||
if %ERRORLEVEL% NEQ 0 (
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
echo Linux build failed!
|
echo Linux build failed!
|
||||||
exit /b %ERRORLEVEL%
|
exit /b %ERRORLEVEL%
|
||||||
@@ -31,7 +31,7 @@ echo.
|
|||||||
echo Copying Windows executables...
|
echo Copying Windows executables...
|
||||||
copy /Y target\release\mcp-memory-server.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe"
|
copy /Y target\release\mcp-memory-server.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe"
|
||||||
copy /Y target\release\mcp-memory-stub.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-stub.exe"
|
copy /Y target\release\mcp-memory-stub.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-stub.exe"
|
||||||
copy /Y target\x86_64-pc-windows-gnu\release\mcp-memory-win-nvim.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-win-nvim.exe"
|
copy /Y target\release\mcp-memory-win-nvim.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-win-nvim.exe"
|
||||||
|
|
||||||
echo Copying Linux executables to WSL...
|
echo Copying Linux executables to WSL...
|
||||||
wsl -d Ubuntu -e bash -c "cp /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory/target/x86_64-unknown-linux-musl/release/mcp-memory-server /home/riz/.local/bin/mcp-memory-server && chmod +x /home/riz/.local/bin/mcp-memory-server"
|
wsl -d Ubuntu -e bash -c "cp /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory/target/x86_64-unknown-linux-musl/release/mcp-memory-server /home/riz/.local/bin/mcp-memory-server && chmod +x /home/riz/.local/bin/mcp-memory-server"
|
||||||
|
|||||||
+277
-314
@@ -8,245 +8,222 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
loop {
|
loop {
|
||||||
let msg = match read_message() {
|
let msg = match read_message().await {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = msg.id.unwrap_or(json!(null));
|
tokio::spawn(async move {
|
||||||
|
let id = msg.id.clone().unwrap_or(json!(null));
|
||||||
|
|
||||||
match msg.method.as_str() {
|
match msg.method.as_str() {
|
||||||
"initialize" => {
|
"initialize" => {
|
||||||
send_response(JsonRpcResponse {
|
send_response(JsonRpcResponse {
|
||||||
jsonrpc: "2.0".to_string(),
|
jsonrpc: "2.0".to_string(),
|
||||||
id,
|
id,
|
||||||
result: Some(json!({
|
result: Some(json!({
|
||||||
"capabilities": {},
|
"capabilities": {},
|
||||||
"serverInfo": {
|
"serverInfo": {
|
||||||
"name": "mcp-memory-linux-nvim",
|
"name": "mcp-memory-linux-nvim",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
}
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
"tools/list" => {
|
|
||||||
send_response(JsonRpcResponse {
|
|
||||||
jsonrpc: "2.0".to_string(),
|
|
||||||
id,
|
|
||||||
result: Some(json!({
|
|
||||||
"tools": [
|
|
||||||
{
|
|
||||||
"name": "nvim_goto_line",
|
|
||||||
"description": "Open a file and jump to a specific line",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"file": { "type": "string" },
|
|
||||||
"line": { "type": "integer" }
|
|
||||||
},
|
|
||||||
"required": ["file", "line"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_get_active_buffer",
|
|
||||||
"description": "Get the contents of the currently active Neovim buffer",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_get_cursor",
|
|
||||||
"description": "Get the current cursor position (line and column) in the active Neovim buffer",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_get_visual_selection",
|
|
||||||
"description": "Get the text that is currently highlighted or was last highlighted in Visual mode",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_set_diagnostics",
|
|
||||||
"description": "Push a diagnostic message (like an LSP warning) to a specific line in the active buffer",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"line": { "type": "integer" },
|
|
||||||
"message": { "type": "string" }
|
|
||||||
},
|
|
||||||
"required": ["line", "message"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
})),
|
||||||
})),
|
error: None,
|
||||||
error: None,
|
}).await;
|
||||||
});
|
}
|
||||||
}
|
"tools/list" => {
|
||||||
"tools/call" => {
|
send_response(JsonRpcResponse {
|
||||||
let params = msg.params.unwrap_or(json!({}));
|
jsonrpc: "2.0".to_string(),
|
||||||
let name = params.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
id,
|
||||||
let args = params.get("arguments").cloned().unwrap_or(json!({}));
|
result: Some(json!({
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "nvim_goto_line",
|
||||||
|
"description": "Open a file and jump to a specific line",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file": { "type": "string" },
|
||||||
|
"line": { "type": "integer" }
|
||||||
|
},
|
||||||
|
"required": ["file", "line"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_get_active_buffer",
|
||||||
|
"description": "Get the contents of the currently active Neovim buffer",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_get_cursor",
|
||||||
|
"description": "Get the current cursor position (line and column) in the active Neovim buffer",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_get_visual_selection",
|
||||||
|
"description": "Get the text that is currently highlighted or was last highlighted in Visual mode",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_set_diagnostics",
|
||||||
|
"description": "Push a diagnostic message (like an LSP warning) to a specific line in the active buffer",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"line": { "type": "integer" },
|
||||||
|
"message": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": ["line", "message"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})),
|
||||||
|
error: None,
|
||||||
|
}).await;
|
||||||
|
}
|
||||||
|
"tools/call" => {
|
||||||
|
let params = msg.params.clone().unwrap_or(json!({}));
|
||||||
|
let name = params.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let args = params.get("arguments").cloned().unwrap_or(json!({}));
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
"nvim_goto_line" => {
|
"nvim_goto_line" => {
|
||||||
let file = args.get("file").and_then(|v| v.as_str()).unwrap_or("");
|
let file = args.get("file").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
||||||
|
|
||||||
match send_nvim_command(&format!("edit +{} {}", line, file)).await {
|
let cmd = format!("edit {} | {} | normal! zz", file, line);
|
||||||
Ok(_) => {
|
match send_nvim_command(&cmd).await {
|
||||||
send_response(JsonRpcResponse {
|
Ok(_) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_response(JsonRpcResponse {
|
||||||
id,
|
jsonrpc: "2.0".to_string(),
|
||||||
result: Some(json!({
|
id,
|
||||||
"content": [{"type": "text", "text": format!("Successfully jumped to {}:{}", file, line)}]
|
result: Some(json!({
|
||||||
})),
|
"content": [
|
||||||
error: None,
|
{ "type": "text", "text": format!("Successfully jumped to {}:{}", file, line) }
|
||||||
});
|
]
|
||||||
}
|
})),
|
||||||
Err(e) => {
|
error: None,
|
||||||
send_response(JsonRpcResponse {
|
}).await;
|
||||||
jsonrpc: "2.0".to_string(),
|
}
|
||||||
id,
|
Err(e) => {
|
||||||
result: Some(json!({
|
send_error(id, -32603, &format!("Failed to execute command: {}", e)).await;
|
||||||
"isError": true,
|
}
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_get_active_buffer" => {
|
||||||
"nvim_get_active_buffer" => {
|
match get_nvim_active_buffer().await {
|
||||||
match get_nvim_active_buffer().await {
|
Ok(content) => {
|
||||||
Ok(content) => {
|
send_response(JsonRpcResponse {
|
||||||
send_response(JsonRpcResponse {
|
jsonrpc: "2.0".to_string(),
|
||||||
jsonrpc: "2.0".to_string(),
|
id,
|
||||||
id,
|
result: Some(json!({
|
||||||
result: Some(json!({
|
"content": [
|
||||||
"content": [{"type": "text", "text": content}]
|
{ "type": "text", "text": content }
|
||||||
})),
|
]
|
||||||
error: None,
|
})),
|
||||||
});
|
error: None,
|
||||||
}
|
}).await;
|
||||||
Err(e) => {
|
}
|
||||||
send_response(JsonRpcResponse {
|
Err(e) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_error(id, -32603, &format!("Failed to get active buffer: {}", e)).await;
|
||||||
id,
|
}
|
||||||
result: Some(json!({
|
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_get_cursor" => {
|
||||||
"nvim_get_cursor" => {
|
match get_nvim_cursor().await {
|
||||||
match get_nvim_cursor().await {
|
Ok(content) => {
|
||||||
Ok(content) => {
|
send_response(JsonRpcResponse {
|
||||||
send_response(JsonRpcResponse {
|
jsonrpc: "2.0".to_string(),
|
||||||
jsonrpc: "2.0".to_string(),
|
id,
|
||||||
id,
|
result: Some(json!({
|
||||||
result: Some(json!({
|
"content": [
|
||||||
"content": [{"type": "text", "text": content}]
|
{ "type": "text", "text": content }
|
||||||
})),
|
]
|
||||||
error: None,
|
})),
|
||||||
});
|
error: None,
|
||||||
}
|
}).await;
|
||||||
Err(e) => {
|
}
|
||||||
send_response(JsonRpcResponse {
|
Err(e) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_error(id, -32603, &format!("Failed to get cursor: {}", e)).await;
|
||||||
id,
|
}
|
||||||
result: Some(json!({
|
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_get_visual_selection" => {
|
||||||
"nvim_get_visual_selection" => {
|
match get_nvim_visual_selection().await {
|
||||||
match get_nvim_visual_selection().await {
|
Ok(content) => {
|
||||||
Ok(content) => {
|
send_response(JsonRpcResponse {
|
||||||
send_response(JsonRpcResponse {
|
jsonrpc: "2.0".to_string(),
|
||||||
jsonrpc: "2.0".to_string(),
|
id,
|
||||||
id,
|
result: Some(json!({
|
||||||
result: Some(json!({
|
"content": [
|
||||||
"content": [{"type": "text", "text": content}]
|
{ "type": "text", "text": content }
|
||||||
})),
|
]
|
||||||
error: None,
|
})),
|
||||||
});
|
error: None,
|
||||||
}
|
}).await;
|
||||||
Err(e) => {
|
}
|
||||||
send_response(JsonRpcResponse {
|
Err(e) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_error(id, -32603, &format!("Failed to get visual selection: {}", e)).await;
|
||||||
id,
|
}
|
||||||
result: Some(json!({
|
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_set_diagnostics" => {
|
||||||
"nvim_set_diagnostics" => {
|
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
||||||
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
let message = args.get("message").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
let message = args.get("message").and_then(|v| v.as_str()).unwrap_or("");
|
|
||||||
|
|
||||||
match set_nvim_diagnostics(line, message).await {
|
match set_nvim_diagnostics(line, message).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
send_response(JsonRpcResponse {
|
send_response(JsonRpcResponse {
|
||||||
jsonrpc: "2.0".to_string(),
|
jsonrpc: "2.0".to_string(),
|
||||||
id,
|
id,
|
||||||
result: Some(json!({
|
result: Some(json!({
|
||||||
"content": [{"type": "text", "text": format!("Successfully pushed diagnostic to line {}", line)}]
|
"content": [
|
||||||
})),
|
{ "type": "text", "text": format!("Successfully set diagnostic on line {}", line) }
|
||||||
error: None,
|
]
|
||||||
});
|
})),
|
||||||
}
|
error: None,
|
||||||
Err(e) => {
|
}).await;
|
||||||
send_response(JsonRpcResponse {
|
}
|
||||||
jsonrpc: "2.0".to_string(),
|
Err(e) => {
|
||||||
id,
|
send_error(id, -32603, &format!("Failed to set diagnostic: {}", e)).await;
|
||||||
result: Some(json!({
|
}
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
_ => {
|
||||||
_ => {
|
send_error(id, -32601, "Tool not found").await;
|
||||||
send_error(id, -32601, "Tool not found");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other methods
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
});
|
||||||
// Ignore other methods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_socket_path() -> Result<String, String> {
|
async fn get_socket_path() -> Result<String, String> {
|
||||||
if let Ok(content) = std::fs::read_to_string(format!("{}/.gemini/active_nvim.txt", std::env::var("HOME").unwrap_or("/home/riz".into()))) {
|
// 1. Try active_nvim.txt first
|
||||||
let p = content.trim().to_string();
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
if !p.is_empty() {
|
let path = format!("{}/.gemini/active_nvim.txt", home);
|
||||||
return Ok(p);
|
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||||
|
let p = content.trim().to_string();
|
||||||
|
if !p.is_empty() {
|
||||||
|
return Ok(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: search /tmp/nvim.*/0
|
// 2. Fallback: search /tmp/nvim.*/0
|
||||||
if let Ok(entries) = std::fs::read_dir("/tmp") {
|
if let Ok(entries) = std::fs::read_dir("/tmp") {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
if let Ok(name) = entry.file_name().into_string() {
|
if let Ok(name) = entry.file_name().into_string() {
|
||||||
@@ -262,33 +239,70 @@ async fn get_socket_path() -> Result<String, String> {
|
|||||||
Err("Could not find Neovim socket".to_string())
|
Err("Could not find Neovim socket".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_nvim_command(cmd: &str) -> Result<(), String> {
|
async fn call_nvim(req: rmpv::Value) -> Result<rmpv::Value, String> {
|
||||||
|
let msgid = if let rmpv::Value::Array(ref arr) = req {
|
||||||
|
if arr.len() > 1 { arr[1].clone() } else { rmpv::Value::Nil }
|
||||||
|
} else { rmpv::Value::Nil };
|
||||||
|
|
||||||
let socket_path = get_socket_path().await?;
|
let socket_path = get_socket_path().await?;
|
||||||
let mut stream = UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?;
|
let mut stream = UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
use rmpv::Value as RmpValue;
|
|
||||||
let req = RmpValue::Array(vec![
|
|
||||||
RmpValue::Integer(0.into()),
|
|
||||||
RmpValue::Integer(1.into()),
|
|
||||||
RmpValue::String("nvim_command".into()),
|
|
||||||
RmpValue::Array(vec![RmpValue::String(cmd.into())]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
||||||
stream.write_all(&buf).await.map_err(|e| e.to_string())?;
|
stream.write_all(&buf).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(())
|
let mut resp_buf = Vec::new();
|
||||||
|
let mut chunk = vec![0u8; 8192];
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut cursor = std::io::Cursor::new(&resp_buf[offset..]);
|
||||||
|
match rmpv::decode::read_value(&mut cursor) {
|
||||||
|
Ok(val) => {
|
||||||
|
offset += cursor.position() as usize;
|
||||||
|
|
||||||
|
if let rmpv::Value::Array(ref arr) = val {
|
||||||
|
if arr.len() >= 4 && arr[0] == rmpv::Value::Integer(1.into()) && arr[1] == msgid {
|
||||||
|
return Ok(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
let n = stream.read(&mut chunk).await.map_err(|e| e.to_string())?;
|
||||||
|
if n == 0 {
|
||||||
|
return Err("Unexpected EOF from Neovim".to_string());
|
||||||
|
}
|
||||||
|
resp_buf.extend_from_slice(&chunk[..n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_nvim_active_buffer() -> Result<String, String> {
|
async fn send_nvim_command(cmd: &str) -> Result<(), String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut stream = UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(2.into()),
|
RmpValue::Integer(1.into()), // msgid
|
||||||
|
RmpValue::String("nvim_command".into()),
|
||||||
|
RmpValue::Array(vec![RmpValue::String(cmd.into())]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let resp = call_nvim(req).await?;
|
||||||
|
if let RmpValue::Array(arr) = resp {
|
||||||
|
if !arr[2].is_nil() {
|
||||||
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err("Invalid response".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_nvim_active_buffer() -> Result<String, String> {
|
||||||
|
use rmpv::Value as RmpValue;
|
||||||
|
let req = RmpValue::Array(vec![
|
||||||
|
RmpValue::Integer(0.into()),
|
||||||
|
RmpValue::Integer(2.into()), // msgid
|
||||||
RmpValue::String("nvim_buf_get_lines".into()),
|
RmpValue::String("nvim_buf_get_lines".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
@@ -298,81 +312,55 @@ async fn get_nvim_active_buffer() -> Result<String, String> {
|
|||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
stream.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 1024 * 1024];
|
|
||||||
let n = stream.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
}
|
||||||
}
|
if let RmpValue::Array(lines) = &arr[3] {
|
||||||
if let RmpValue::Array(lines) = &arr[3] {
|
let mut text = String::new();
|
||||||
let mut text = String::new();
|
for line in lines {
|
||||||
for line in lines {
|
if let RmpValue::String(s) = line {
|
||||||
if let RmpValue::String(s) = line {
|
if let Some(s) = s.as_str() {
|
||||||
if let Some(s) = s.as_str() {
|
text.push_str(s);
|
||||||
text.push_str(s);
|
text.push('\n');
|
||||||
text.push('\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(text);
|
|
||||||
}
|
}
|
||||||
|
return Ok(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err("Invalid response from Neovim".to_string())
|
Err("Invalid response".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_nvim_cursor() -> Result<String, String> {
|
async fn get_nvim_cursor() -> Result<String, String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut stream = UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(2.into()),
|
RmpValue::Integer(3.into()), // msgid
|
||||||
RmpValue::String("nvim_win_get_cursor".into()),
|
RmpValue::String("nvim_win_get_cursor".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
stream.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 8192];
|
|
||||||
let n = stream.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
}
|
||||||
}
|
if let RmpValue::Array(pos) = &arr[3] {
|
||||||
if let RmpValue::Array(pos) = &arr[3] {
|
if pos.len() == 2 {
|
||||||
if pos.len() == 2 {
|
if let (RmpValue::Integer(row), RmpValue::Integer(col)) = (&pos[0], &pos[1]) {
|
||||||
if let (RmpValue::Integer(row), RmpValue::Integer(col)) = (&pos[0], &pos[1]) {
|
return Ok(format!("Line: {}, Column: {}", row, col));
|
||||||
return Ok(format!("Line: {}, Column: {}", row, col));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err("Invalid response from Neovim".to_string())
|
Err("Invalid response".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_nvim_visual_selection() -> Result<String, String> {
|
async fn get_nvim_visual_selection() -> Result<String, String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut stream = UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let lua_script = r#"
|
let lua_script = r#"
|
||||||
local _, csrow, cscol, _ = unpack(vim.fn.getpos("'<"))
|
local _, csrow, cscol, _ = unpack(vim.fn.getpos("'<"))
|
||||||
local _, cerow, cecol, _ = unpack(vim.fn.getpos("'>"))
|
local _, cerow, cecol, _ = unpack(vim.fn.getpos("'>"))
|
||||||
@@ -387,7 +375,7 @@ async fn get_nvim_visual_selection() -> Result<String, String> {
|
|||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(3.into()),
|
RmpValue::Integer(4.into()), // msgid
|
||||||
RmpValue::String("nvim_exec_lua".into()),
|
RmpValue::String("nvim_exec_lua".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::String(lua_script.into()),
|
RmpValue::String(lua_script.into()),
|
||||||
@@ -395,37 +383,22 @@ async fn get_nvim_visual_selection() -> Result<String, String> {
|
|||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
stream.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 1024 * 1024];
|
|
||||||
let n = stream.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
}
|
||||||
}
|
if let RmpValue::String(s) = &arr[3] {
|
||||||
if let RmpValue::String(s) = &arr[3] {
|
if let Some(text) = s.as_str() {
|
||||||
if let Some(text) = s.as_str() {
|
return Ok(text.to_string());
|
||||||
return Ok(text.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err("Invalid response from Neovim".to_string())
|
Err("Invalid response".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut stream = UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
// We escape backslashes and double quotes in the message
|
|
||||||
let escaped_message = message.replace("\\", "\\\\").replace("\"", "\\\"");
|
let escaped_message = message.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
|
|
||||||
let lua_script = format!(r#"
|
let lua_script = format!(r#"
|
||||||
local ns = vim.api.nvim_create_namespace("gemini_diagnostics")
|
local ns = vim.api.nvim_create_namespace("gemini_diagnostics")
|
||||||
local diagnostics = {{{{
|
local diagnostics = {{{{
|
||||||
@@ -440,7 +413,7 @@ async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
|||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(4.into()),
|
RmpValue::Integer(5.into()), // msgid
|
||||||
RmpValue::String("nvim_exec_lua".into()),
|
RmpValue::String("nvim_exec_lua".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::String(lua_script.into()),
|
RmpValue::String(lua_script.into()),
|
||||||
@@ -448,22 +421,12 @@ async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
|||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
stream.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 8192];
|
|
||||||
let n = stream.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err("Invalid response from Neovim".to_string())
|
Err("Invalid response".to_string())
|
||||||
}
|
}
|
||||||
+12
-12
@@ -1,5 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct JsonRpcRequest {
|
pub struct JsonRpcRequest {
|
||||||
@@ -19,14 +20,12 @@ pub struct JsonRpcResponse {
|
|||||||
pub error: Option<Value>,
|
pub error: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_message() -> Option<JsonRpcRequest> {
|
pub async fn read_message() -> Option<JsonRpcRequest> {
|
||||||
use std::io::{BufRead, Read};
|
let mut stdin = BufReader::new(tokio::io::stdin());
|
||||||
let stdin = std::io::stdin();
|
|
||||||
let mut handle = stdin.lock();
|
|
||||||
let mut length = 0;
|
let mut length = 0;
|
||||||
loop {
|
loop {
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
if handle.read_line(&mut line).unwrap_or(0) == 0 {
|
if stdin.read_line(&mut line).await.unwrap_or(0) == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let line = line.trim_end();
|
let line = line.trim_end();
|
||||||
@@ -41,22 +40,23 @@ pub fn read_message() -> Option<JsonRpcRequest> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut buffer = vec![0; length];
|
let mut buffer = vec![0; length];
|
||||||
handle.read_exact(&mut buffer).unwrap_or(());
|
stdin.read_exact(&mut buffer).await.unwrap_or(0);
|
||||||
serde_json::from_slice(&buffer).ok()
|
serde_json::from_slice(&buffer).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_response(response: JsonRpcResponse) {
|
pub async fn send_response(response: JsonRpcResponse) {
|
||||||
let msg = serde_json::to_string(&response).unwrap();
|
let msg = serde_json::to_string(&response).unwrap();
|
||||||
print!("Content-Length: {}\r\n\r\n{}", msg.len(), msg);
|
let payload = format!("Content-Length: {}\r\n\r\n{}", msg.len(), msg);
|
||||||
use std::io::Write;
|
let mut stdout = tokio::io::stdout();
|
||||||
std::io::stdout().flush().unwrap();
|
let _ = stdout.write_all(payload.as_bytes()).await;
|
||||||
|
let _ = stdout.flush().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_error(id: Value, code: i32, message: &str) {
|
pub async fn send_error(id: Value, code: i32, message: &str) {
|
||||||
send_response(JsonRpcResponse {
|
send_response(JsonRpcResponse {
|
||||||
jsonrpc: "2.0".to_string(),
|
jsonrpc: "2.0".to_string(),
|
||||||
id,
|
id,
|
||||||
result: None,
|
result: None,
|
||||||
error: Some(serde_json::json!({"code": code, "message": message})),
|
error: Some(serde_json::json!({"code": code, "message": message})),
|
||||||
});
|
}).await;
|
||||||
}
|
}
|
||||||
+28
-25
@@ -24,48 +24,51 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let (msg_tx, mut msg_rx) = mpsc::channel::<String>(100);
|
let (msg_tx, mut msg_rx) = mpsc::channel::<String>(100);
|
||||||
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
|
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::spawn(async move {
|
||||||
let stdin = std::io::stdin();
|
let mut stdin = tokio::io::BufReader::new(tokio::io::stdin());
|
||||||
let mut handle = stdin.lock();
|
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
while let Ok(bytes) = std::io::BufRead::read_line(&mut handle, &mut buffer) {
|
while let Ok(bytes) = stdin.read_line(&mut buffer).await {
|
||||||
if bytes == 0 {
|
if bytes == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let _ = msg_tx.blocking_send(buffer.clone());
|
let _ = msg_tx.send(buffer.clone()).await;
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
let _ = shutdown_tx.blocking_send(());
|
let _ = shutdown_tx.send(()).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
let target_url = cli.target;
|
let target_url = cli.target;
|
||||||
let post_url = Arc::new(RwLock::new(String::new()));
|
let post_url = Arc::new(RwLock::new(String::new()));
|
||||||
let post_url_clone = Arc::clone(&post_url);
|
let post_url_proxy = Arc::clone(&post_url);
|
||||||
|
|
||||||
let client = reqwest::Client::builder().build()?;
|
let client = reqwest::Client::builder().build()?;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(msg) = msg_rx.recv().await {
|
while let Some(msg) = msg_rx.recv().await {
|
||||||
let mut attempts = 0;
|
let post_url_clone = Arc::clone(&post_url_proxy);
|
||||||
loop {
|
let client = client.clone();
|
||||||
let url = post_url_clone.read().await.clone();
|
tokio::spawn(async move {
|
||||||
if !url.is_empty() {
|
let mut attempts = 0;
|
||||||
let res = client
|
loop {
|
||||||
.post(&url)
|
let url = post_url_clone.read().await.clone();
|
||||||
.header("Accept", "application/json, text/event-stream")
|
if !url.is_empty() {
|
||||||
.header("Content-Type", "application/json")
|
let res = client
|
||||||
.body(msg.clone())
|
.post(&url)
|
||||||
.send()
|
.header("Accept", "application/json, text/event-stream")
|
||||||
.await;
|
.header("Content-Type", "application/json")
|
||||||
|
.body(msg.clone())
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
if let Ok(resp) = res { if resp.status().is_success() { break; } }
|
if let Ok(resp) = res { if resp.status().is_success() { break; } }
|
||||||
|
}
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||||
|
attempts += 1;
|
||||||
|
if attempts % 10 == 0 {
|
||||||
|
eprintln!("[PROXY] Waiting for server to accept messages...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
});
|
||||||
attempts += 1;
|
|
||||||
if attempts % 10 == 0 {
|
|
||||||
eprintln!("[PROXY] Waiting for server to accept messages...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+281
-311
@@ -8,237 +8,222 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
loop {
|
loop {
|
||||||
let msg = match read_message() {
|
let msg = match read_message().await {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = msg.id.unwrap_or(json!(null));
|
tokio::spawn(async move {
|
||||||
|
let id = msg.id.clone().unwrap_or(json!(null));
|
||||||
|
|
||||||
match msg.method.as_str() {
|
match msg.method.as_str() {
|
||||||
"initialize" => {
|
"initialize" => {
|
||||||
send_response(JsonRpcResponse {
|
send_response(JsonRpcResponse {
|
||||||
jsonrpc: "2.0".to_string(),
|
jsonrpc: "2.0".to_string(),
|
||||||
id,
|
id,
|
||||||
result: Some(json!({
|
result: Some(json!({
|
||||||
"capabilities": {},
|
"capabilities": {},
|
||||||
"serverInfo": {
|
"serverInfo": {
|
||||||
"name": "mcp-memory-win-nvim",
|
"name": "mcp-memory-win-nvim",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
}
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
"tools/list" => {
|
|
||||||
send_response(JsonRpcResponse {
|
|
||||||
jsonrpc: "2.0".to_string(),
|
|
||||||
id,
|
|
||||||
result: Some(json!({
|
|
||||||
"tools": [
|
|
||||||
{
|
|
||||||
"name": "nvim_goto_line",
|
|
||||||
"description": "Open a file and jump to a specific line",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"file": { "type": "string" },
|
|
||||||
"line": { "type": "integer" }
|
|
||||||
},
|
|
||||||
"required": ["file", "line"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_get_active_buffer",
|
|
||||||
"description": "Get the contents of the currently active Neovim buffer",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_get_cursor",
|
|
||||||
"description": "Get the current cursor position (line and column) in the active Neovim buffer",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_get_visual_selection",
|
|
||||||
"description": "Get the text that is currently highlighted or was last highlighted in Visual mode",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nvim_set_diagnostics",
|
|
||||||
"description": "Push a diagnostic message (like an LSP warning) to a specific line in the active buffer",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"line": { "type": "integer" },
|
|
||||||
"message": { "type": "string" }
|
|
||||||
},
|
|
||||||
"required": ["line", "message"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
})),
|
||||||
})),
|
error: None,
|
||||||
error: None,
|
}).await;
|
||||||
});
|
}
|
||||||
}
|
"tools/list" => {
|
||||||
"tools/call" => {
|
send_response(JsonRpcResponse {
|
||||||
let params = msg.params.unwrap_or(json!({}));
|
jsonrpc: "2.0".to_string(),
|
||||||
let name = params.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
id,
|
||||||
let args = params.get("arguments").cloned().unwrap_or(json!({}));
|
result: Some(json!({
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "nvim_goto_line",
|
||||||
|
"description": "Open a file and jump to a specific line",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file": { "type": "string" },
|
||||||
|
"line": { "type": "integer" }
|
||||||
|
},
|
||||||
|
"required": ["file", "line"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_get_active_buffer",
|
||||||
|
"description": "Get the contents of the currently active Neovim buffer",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_get_cursor",
|
||||||
|
"description": "Get the current cursor position (line and column) in the active Neovim buffer",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_get_visual_selection",
|
||||||
|
"description": "Get the text that is currently highlighted or was last highlighted in Visual mode",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nvim_set_diagnostics",
|
||||||
|
"description": "Push a diagnostic message (like an LSP warning) to a specific line in the active buffer",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"line": { "type": "integer" },
|
||||||
|
"message": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": ["line", "message"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})),
|
||||||
|
error: None,
|
||||||
|
}).await;
|
||||||
|
}
|
||||||
|
"tools/call" => {
|
||||||
|
let params = msg.params.clone().unwrap_or(json!({}));
|
||||||
|
let name = params.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let args = params.get("arguments").cloned().unwrap_or(json!({}));
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
"nvim_goto_line" => {
|
"nvim_goto_line" => {
|
||||||
let file = args.get("file").and_then(|v| v.as_str()).unwrap_or("");
|
let file = args.get("file").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
||||||
|
|
||||||
match send_nvim_command(&format!("edit +{} {}", line, file)).await {
|
let cmd = format!("edit {} | {} | normal! zz", file, line);
|
||||||
Ok(_) => {
|
match send_nvim_command(&cmd).await {
|
||||||
send_response(JsonRpcResponse {
|
Ok(_) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_response(JsonRpcResponse {
|
||||||
id,
|
jsonrpc: "2.0".to_string(),
|
||||||
result: Some(json!({
|
id,
|
||||||
"content": [{"type": "text", "text": format!("Successfully jumped to {}:{}", file, line)}]
|
result: Some(json!({
|
||||||
})),
|
"content": [
|
||||||
error: None,
|
{ "type": "text", "text": format!("Successfully jumped to {}:{}", file, line) }
|
||||||
});
|
]
|
||||||
}
|
})),
|
||||||
Err(e) => {
|
error: None,
|
||||||
send_response(JsonRpcResponse {
|
}).await;
|
||||||
jsonrpc: "2.0".to_string(),
|
}
|
||||||
id,
|
Err(e) => {
|
||||||
result: Some(json!({
|
send_error(id, -32603, &format!("Failed to execute command: {}", e)).await;
|
||||||
"isError": true,
|
}
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_get_active_buffer" => {
|
||||||
"nvim_get_active_buffer" => {
|
match get_nvim_active_buffer().await {
|
||||||
match get_nvim_active_buffer().await {
|
Ok(content) => {
|
||||||
Ok(content) => {
|
send_response(JsonRpcResponse {
|
||||||
send_response(JsonRpcResponse {
|
jsonrpc: "2.0".to_string(),
|
||||||
jsonrpc: "2.0".to_string(),
|
id,
|
||||||
id,
|
result: Some(json!({
|
||||||
result: Some(json!({
|
"content": [
|
||||||
"content": [{"type": "text", "text": content}]
|
{ "type": "text", "text": content }
|
||||||
})),
|
]
|
||||||
error: None,
|
})),
|
||||||
});
|
error: None,
|
||||||
}
|
}).await;
|
||||||
Err(e) => {
|
}
|
||||||
send_response(JsonRpcResponse {
|
Err(e) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_error(id, -32603, &format!("Failed to get active buffer: {}", e)).await;
|
||||||
id,
|
}
|
||||||
result: Some(json!({
|
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_get_cursor" => {
|
||||||
"nvim_get_cursor" => {
|
match get_nvim_cursor().await {
|
||||||
match get_nvim_cursor().await {
|
Ok(content) => {
|
||||||
Ok(content) => {
|
send_response(JsonRpcResponse {
|
||||||
send_response(JsonRpcResponse {
|
jsonrpc: "2.0".to_string(),
|
||||||
jsonrpc: "2.0".to_string(),
|
id,
|
||||||
id,
|
result: Some(json!({
|
||||||
result: Some(json!({
|
"content": [
|
||||||
"content": [{"type": "text", "text": content}]
|
{ "type": "text", "text": content }
|
||||||
})),
|
]
|
||||||
error: None,
|
})),
|
||||||
});
|
error: None,
|
||||||
}
|
}).await;
|
||||||
Err(e) => {
|
}
|
||||||
send_response(JsonRpcResponse {
|
Err(e) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_error(id, -32603, &format!("Failed to get cursor: {}", e)).await;
|
||||||
id,
|
}
|
||||||
result: Some(json!({
|
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_get_visual_selection" => {
|
||||||
"nvim_get_visual_selection" => {
|
match get_nvim_visual_selection().await {
|
||||||
match get_nvim_visual_selection().await {
|
Ok(content) => {
|
||||||
Ok(content) => {
|
send_response(JsonRpcResponse {
|
||||||
send_response(JsonRpcResponse {
|
jsonrpc: "2.0".to_string(),
|
||||||
jsonrpc: "2.0".to_string(),
|
id,
|
||||||
id,
|
result: Some(json!({
|
||||||
result: Some(json!({
|
"content": [
|
||||||
"content": [{"type": "text", "text": content}]
|
{ "type": "text", "text": content }
|
||||||
})),
|
]
|
||||||
error: None,
|
})),
|
||||||
});
|
error: None,
|
||||||
}
|
}).await;
|
||||||
Err(e) => {
|
}
|
||||||
send_response(JsonRpcResponse {
|
Err(e) => {
|
||||||
jsonrpc: "2.0".to_string(),
|
send_error(id, -32603, &format!("Failed to get visual selection: {}", e)).await;
|
||||||
id,
|
}
|
||||||
result: Some(json!({
|
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
"nvim_set_diagnostics" => {
|
||||||
"nvim_set_diagnostics" => {
|
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
||||||
let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(1);
|
let message = args.get("message").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
let message = args.get("message").and_then(|v| v.as_str()).unwrap_or("");
|
|
||||||
|
|
||||||
match set_nvim_diagnostics(line, message).await {
|
match set_nvim_diagnostics(line, message).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
send_response(JsonRpcResponse {
|
send_response(JsonRpcResponse {
|
||||||
jsonrpc: "2.0".to_string(),
|
jsonrpc: "2.0".to_string(),
|
||||||
id,
|
id,
|
||||||
result: Some(json!({
|
result: Some(json!({
|
||||||
"content": [{"type": "text", "text": format!("Successfully pushed diagnostic to line {}", line)}]
|
"content": [
|
||||||
})),
|
{ "type": "text", "text": format!("Successfully set diagnostic on line {}", line) }
|
||||||
error: None,
|
]
|
||||||
});
|
})),
|
||||||
}
|
error: None,
|
||||||
Err(e) => {
|
}).await;
|
||||||
send_response(JsonRpcResponse {
|
}
|
||||||
jsonrpc: "2.0".to_string(),
|
Err(e) => {
|
||||||
id,
|
send_error(id, -32603, &format!("Failed to set diagnostic: {}", e)).await;
|
||||||
result: Some(json!({
|
}
|
||||||
"isError": true,
|
|
||||||
"content": [{"type": "text", "text": format!("Error: {}", e)}]
|
|
||||||
})),
|
|
||||||
error: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
_ => {
|
||||||
_ => {
|
send_error(id, -32601, "Tool not found").await;
|
||||||
send_error(id, -32601, "Tool not found");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other methods
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
});
|
||||||
// Ignore other methods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_socket_path() -> Result<String, String> {
|
async fn get_socket_path() -> Result<String, String> {
|
||||||
|
// 1. Try auto-discovery in \\.\pipe\
|
||||||
|
if let Ok(dir) = std::fs::read_dir(r"\\.\pipe\") {
|
||||||
|
for entry in dir.flatten() {
|
||||||
|
let name = entry.file_name();
|
||||||
|
let name_str = name.to_string_lossy();
|
||||||
|
if name_str.starts_with("nvim.") {
|
||||||
|
return Ok(format!(r"\\.\pipe\{}", name_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fallback to active_nvim.txt
|
||||||
let profile = std::env::var("USERPROFILE").unwrap_or_else(|_| "C:\\Users\\reazul.ashraf".into());
|
let profile = std::env::var("USERPROFILE").unwrap_or_else(|_| "C:\\Users\\reazul.ashraf".into());
|
||||||
let path = format!("{}\\.gemini\\active_nvim.txt", profile);
|
let path = format!("{}\\.gemini\\active_nvim.txt", profile);
|
||||||
|
|
||||||
@@ -249,36 +234,73 @@ async fn get_socket_path() -> Result<String, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("Could not find active Windows Neovim named pipe in active_nvim.txt".to_string())
|
Err("Could not find active Windows Neovim named pipe".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_nvim_command(cmd: &str) -> Result<(), String> {
|
async fn call_nvim(req: rmpv::Value) -> Result<rmpv::Value, String> {
|
||||||
|
let msgid = if let rmpv::Value::Array(ref arr) = req {
|
||||||
|
if arr.len() > 1 { arr[1].clone() } else { rmpv::Value::Nil }
|
||||||
|
} else { rmpv::Value::Nil };
|
||||||
|
|
||||||
let socket_path = get_socket_path().await?;
|
let socket_path = get_socket_path().await?;
|
||||||
let mut client = ClientOptions::new().open(&socket_path).map_err(|e| e.to_string())?;
|
let mut client = ClientOptions::new().open(&socket_path).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
use rmpv::Value as RmpValue;
|
|
||||||
let req = RmpValue::Array(vec![
|
|
||||||
RmpValue::Integer(0.into()),
|
|
||||||
RmpValue::Integer(1.into()),
|
|
||||||
RmpValue::String("nvim_command".into()),
|
|
||||||
RmpValue::Array(vec![RmpValue::String(cmd.into())]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
||||||
client.write_all(&buf).await.map_err(|e| e.to_string())?;
|
client.write_all(&buf).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(())
|
let mut resp_buf = Vec::new();
|
||||||
|
let mut chunk = vec![0u8; 8192];
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut cursor = std::io::Cursor::new(&resp_buf[offset..]);
|
||||||
|
match rmpv::decode::read_value(&mut cursor) {
|
||||||
|
Ok(val) => {
|
||||||
|
offset += cursor.position() as usize;
|
||||||
|
|
||||||
|
if let rmpv::Value::Array(ref arr) = val {
|
||||||
|
if arr.len() >= 4 && arr[0] == rmpv::Value::Integer(1.into()) && arr[1] == msgid {
|
||||||
|
return Ok(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
let n = client.read(&mut chunk).await.map_err(|e| e.to_string())?;
|
||||||
|
if n == 0 {
|
||||||
|
return Err("Unexpected EOF from Neovim".to_string());
|
||||||
|
}
|
||||||
|
resp_buf.extend_from_slice(&chunk[..n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_nvim_active_buffer() -> Result<String, String> {
|
async fn send_nvim_command(cmd: &str) -> Result<(), String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut client = ClientOptions::new().open(&socket_path).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(2.into()),
|
RmpValue::Integer(1.into()), // msgid
|
||||||
|
RmpValue::String("nvim_command".into()),
|
||||||
|
RmpValue::Array(vec![RmpValue::String(cmd.into())]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let resp = call_nvim(req).await?;
|
||||||
|
if let RmpValue::Array(arr) = resp {
|
||||||
|
if !arr[2].is_nil() {
|
||||||
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err("Invalid response".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_nvim_active_buffer() -> Result<String, String> {
|
||||||
|
use rmpv::Value as RmpValue;
|
||||||
|
let req = RmpValue::Array(vec![
|
||||||
|
RmpValue::Integer(0.into()),
|
||||||
|
RmpValue::Integer(2.into()), // msgid
|
||||||
RmpValue::String("nvim_buf_get_lines".into()),
|
RmpValue::String("nvim_buf_get_lines".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
@@ -288,83 +310,55 @@ async fn get_nvim_active_buffer() -> Result<String, String> {
|
|||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
client.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 1024 * 1024]; // 1MB buffer
|
|
||||||
let n = client.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
}
|
||||||
}
|
if let RmpValue::Array(lines) = &arr[3] {
|
||||||
if let RmpValue::Array(lines) = &arr[3] {
|
let mut text = String::new();
|
||||||
let mut text = String::new();
|
for line in lines {
|
||||||
for line in lines {
|
if let RmpValue::String(s) = line {
|
||||||
if let RmpValue::String(s) = line {
|
if let Some(s) = s.as_str() {
|
||||||
if let Some(s) = s.as_str() {
|
text.push_str(s);
|
||||||
text.push_str(s);
|
text.push('\n');
|
||||||
text.push('\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(text);
|
|
||||||
}
|
}
|
||||||
|
return Ok(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err("Invalid response".to_string())
|
||||||
Err("Invalid response from Neovim".to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_nvim_cursor() -> Result<String, String> {
|
async fn get_nvim_cursor() -> Result<String, String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut client = ClientOptions::new().open(&socket_path).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(2.into()),
|
RmpValue::Integer(3.into()), // msgid
|
||||||
RmpValue::String("nvim_win_get_cursor".into()),
|
RmpValue::String("nvim_win_get_cursor".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
client.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 8192];
|
|
||||||
let n = client.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
}
|
||||||
}
|
if let RmpValue::Array(pos) = &arr[3] {
|
||||||
if let RmpValue::Array(pos) = &arr[3] {
|
if pos.len() == 2 {
|
||||||
if pos.len() == 2 {
|
if let (RmpValue::Integer(row), RmpValue::Integer(col)) = (&pos[0], &pos[1]) {
|
||||||
if let (RmpValue::Integer(row), RmpValue::Integer(col)) = (&pos[0], &pos[1]) {
|
return Ok(format!("Line: {}, Column: {}", row, col));
|
||||||
return Ok(format!("Line: {}, Column: {}", row, col));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err("Invalid response from Neovim".to_string())
|
Err("Invalid response".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_nvim_visual_selection() -> Result<String, String> {
|
async fn get_nvim_visual_selection() -> Result<String, String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut client = ClientOptions::new().open(&socket_path).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let lua_script = r#"
|
let lua_script = r#"
|
||||||
local _, csrow, cscol, _ = unpack(vim.fn.getpos("'<"))
|
local _, csrow, cscol, _ = unpack(vim.fn.getpos("'<"))
|
||||||
local _, cerow, cecol, _ = unpack(vim.fn.getpos("'>"))
|
local _, cerow, cecol, _ = unpack(vim.fn.getpos("'>"))
|
||||||
@@ -379,7 +373,7 @@ async fn get_nvim_visual_selection() -> Result<String, String> {
|
|||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(3.into()),
|
RmpValue::Integer(4.into()), // msgid
|
||||||
RmpValue::String("nvim_exec_lua".into()),
|
RmpValue::String("nvim_exec_lua".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::String(lua_script.into()),
|
RmpValue::String(lua_script.into()),
|
||||||
@@ -387,36 +381,22 @@ async fn get_nvim_visual_selection() -> Result<String, String> {
|
|||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
client.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 1024 * 1024];
|
|
||||||
let n = client.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
}
|
||||||
}
|
if let RmpValue::String(s) = &arr[3] {
|
||||||
if let RmpValue::String(s) = &arr[3] {
|
if let Some(text) = s.as_str() {
|
||||||
if let Some(text) = s.as_str() {
|
return Ok(text.to_string());
|
||||||
return Ok(text.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err("Invalid response from Neovim".to_string())
|
Err("Invalid response".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
||||||
let socket_path = get_socket_path().await?;
|
|
||||||
let mut client = ClientOptions::new().open(&socket_path).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let escaped_message = message.replace("\\", "\\\\").replace("\"", "\\\"");
|
let escaped_message = message.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
|
|
||||||
let lua_script = format!(r#"
|
let lua_script = format!(r#"
|
||||||
local ns = vim.api.nvim_create_namespace("gemini_diagnostics")
|
local ns = vim.api.nvim_create_namespace("gemini_diagnostics")
|
||||||
local diagnostics = {{{{
|
local diagnostics = {{{{
|
||||||
@@ -431,7 +411,7 @@ async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
|||||||
use rmpv::Value as RmpValue;
|
use rmpv::Value as RmpValue;
|
||||||
let req = RmpValue::Array(vec![
|
let req = RmpValue::Array(vec![
|
||||||
RmpValue::Integer(0.into()),
|
RmpValue::Integer(0.into()),
|
||||||
RmpValue::Integer(4.into()),
|
RmpValue::Integer(5.into()), // msgid
|
||||||
RmpValue::String("nvim_exec_lua".into()),
|
RmpValue::String("nvim_exec_lua".into()),
|
||||||
RmpValue::Array(vec![
|
RmpValue::Array(vec![
|
||||||
RmpValue::String(lua_script.into()),
|
RmpValue::String(lua_script.into()),
|
||||||
@@ -439,22 +419,12 @@ async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
|
|||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let resp = call_nvim(req).await?;
|
||||||
rmpv::encode::write_value(&mut buf, &req).map_err(|e| e.to_string())?;
|
|
||||||
client.write_all(&buf).await.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut resp_buf = [0u8; 8192];
|
|
||||||
let n = client.read(&mut resp_buf).await.map_err(|e| e.to_string())?;
|
|
||||||
let mut cursor = std::io::Cursor::new(&resp_buf[..n]);
|
|
||||||
let resp = rmpv::decode::read_value(&mut cursor).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if let RmpValue::Array(arr) = resp {
|
if let RmpValue::Array(arr) = resp {
|
||||||
if arr.len() >= 4 {
|
if !arr[2].is_nil() {
|
||||||
if !arr[2].is_nil() {
|
return Err(format!("Neovim error: {:?}", arr[2]));
|
||||||
return Err(format!("Neovim error: {:?}", arr[2]));
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err("Invalid response from Neovim".to_string())
|
Err("Invalid response".to_string())
|
||||||
}
|
}
|
||||||
+12
-12
@@ -1,5 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct JsonRpcRequest {
|
pub struct JsonRpcRequest {
|
||||||
@@ -19,14 +20,12 @@ pub struct JsonRpcResponse {
|
|||||||
pub error: Option<Value>,
|
pub error: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_message() -> Option<JsonRpcRequest> {
|
pub async fn read_message() -> Option<JsonRpcRequest> {
|
||||||
use std::io::{BufRead, Read};
|
let mut stdin = BufReader::new(tokio::io::stdin());
|
||||||
let stdin = std::io::stdin();
|
|
||||||
let mut handle = stdin.lock();
|
|
||||||
let mut length = 0;
|
let mut length = 0;
|
||||||
loop {
|
loop {
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
if handle.read_line(&mut line).unwrap_or(0) == 0 {
|
if stdin.read_line(&mut line).await.unwrap_or(0) == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let line = line.trim_end();
|
let line = line.trim_end();
|
||||||
@@ -41,22 +40,23 @@ pub fn read_message() -> Option<JsonRpcRequest> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut buffer = vec![0; length];
|
let mut buffer = vec![0; length];
|
||||||
handle.read_exact(&mut buffer).unwrap_or(());
|
stdin.read_exact(&mut buffer).await.unwrap_or(0);
|
||||||
serde_json::from_slice(&buffer).ok()
|
serde_json::from_slice(&buffer).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_response(response: JsonRpcResponse) {
|
pub async fn send_response(response: JsonRpcResponse) {
|
||||||
let msg = serde_json::to_string(&response).unwrap();
|
let msg = serde_json::to_string(&response).unwrap();
|
||||||
print!("Content-Length: {}\r\n\r\n{}", msg.len(), msg);
|
let payload = format!("Content-Length: {}\r\n\r\n{}", msg.len(), msg);
|
||||||
use std::io::Write;
|
let mut stdout = tokio::io::stdout();
|
||||||
std::io::stdout().flush().unwrap();
|
let _ = stdout.write_all(payload.as_bytes()).await;
|
||||||
|
let _ = stdout.flush().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_error(id: Value, code: i32, message: &str) {
|
pub async fn send_error(id: Value, code: i32, message: &str) {
|
||||||
send_response(JsonRpcResponse {
|
send_response(JsonRpcResponse {
|
||||||
jsonrpc: "2.0".to_string(),
|
jsonrpc: "2.0".to_string(),
|
||||||
id,
|
id,
|
||||||
result: None,
|
result: None,
|
||||||
error: Some(serde_json::json!({"code": code, "message": message})),
|
error: Some(serde_json::json!({"code": code, "message": message})),
|
||||||
});
|
}).await;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user