perf: fix memory leaks, optimize tracing logs, fix networking eof fragmentation bug in nvim-core and stub, and deduplicate json-rpc handlers

This commit is contained in:
Riz Ashraf committed 2026-09-22 04:31:50 +01:00
1 parent ce88bef45e
commit f76d8ccce8
23 files changed
+1373 -803

No files matched your search

+70 -129
View File
@@ -23,13 +23,10 @@ pub struct JsonRpcResponse {
pub async fn send_response(response: JsonRpcResponse) {
let msg = serde_json::to_string(&response).unwrap_or_else(|_| "{}".to_string());
tracing::info!(
"Sending JSON-RPC response (id: {:?}): {}",
"Sending JSON-RPC response (id: {:?}): {}{}",
response.id,
if msg.len() > 500 {
format!("{}...", &msg[..500])
} else {
msg.clone()
}
&msg[..std::cmp::min(msg.len(), 500)],
if msg.len() > 500 { "..." } else { "" }
);
// CRITICAL ARCHITECTURAL DECISION:
// The MCP StdioTransport MUST use Newline-Delimited JSON (NDJSON).
@@ -208,16 +205,20 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
}
}
}
// Trim buffer if it gets too large
if offset > 1024 * 1024 {
if offset == resp_buf.len() {
resp_buf.clear();
offset = 0;
} else if offset > 1024 * 1024 {
resp_buf.drain(..offset);
offset = 0;
}
continue;
}
Err(rmpv::decode::Error::InvalidMarkerRead(e))
if e.kind() == std::io::ErrorKind::UnexpectedEof =>
{
Err(e) if match &e {
rmpv::decode::Error::InvalidMarkerRead(io_err) => io_err.kind() == std::io::ErrorKind::UnexpectedEof,
rmpv::decode::Error::InvalidDataRead(io_err) => io_err.kind() == std::io::ErrorKind::UnexpectedEof,
_ => false,
} => {
resp_buf.drain(..offset);
offset = 0;
@@ -305,86 +306,68 @@ async fn call_nvim(req: rmpv::Value) -> Result<rmpv::Value, String> {
}
}
async fn send_nvim_command(cmd: &str) -> Result<(), String> {
async fn call_nvim_method(method: &str, args: Vec<rmpv::Value>) -> Result<rmpv::Value, String> {
use rmpv::Value as RmpValue;
let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);
let req = RmpValue::Array(vec![
RmpValue::Integer(0.into()),
RmpValue::Integer(msgid.into()), // msgid
RmpValue::String("nvim_command".into()),
RmpValue::Array(vec![RmpValue::String(cmd.into())]),
RmpValue::Integer(msgid.into()),
RmpValue::String(method.into()),
RmpValue::Array(args),
]);
let resp = call_nvim(req).await?;
if let RmpValue::Array(arr) = resp {
if let RmpValue::Array(mut arr) = resp {
if arr.len() < 4 {
return Err("Invalid response length".to_string());
}
if !arr[2].is_nil() {
return Err(format!("Neovim error: {:?}", arr[2]));
}
return Ok(());
return Ok(arr.swap_remove(3));
}
Err("Invalid response".to_string())
Err("Invalid response format".to_string())
}
async fn send_nvim_command(cmd: &str) -> Result<(), String> {
call_nvim_method("nvim_command", vec![rmpv::Value::String(cmd.into())]).await?;
Ok(())
}
async fn get_nvim_active_buffer() -> Result<String, String> {
use rmpv::Value as RmpValue;
let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);
let req = RmpValue::Array(vec![
RmpValue::Integer(0.into()),
RmpValue::Integer(msgid.into()), // msgid
RmpValue::String("nvim_buf_get_lines".into()),
RmpValue::Array(vec![
RmpValue::Integer(0.into()),
RmpValue::Integer(0.into()),
RmpValue::Integer((-1).into()),
RmpValue::Boolean(true),
]),
]);
let resp = call_nvim(req).await?;
if let RmpValue::Array(arr) = resp {
if !arr[2].is_nil() {
return Err(format!("Neovim error: {:?}", arr[2]));
}
if let RmpValue::Array(lines) = &arr[3] {
let mut text = String::new();
for line in lines {
if let RmpValue::String(s) = line {
if let Some(s) = s.as_str() {
text.push_str(s);
text.push('\n');
}
let result = call_nvim_method("nvim_buf_get_lines", vec![
rmpv::Value::Integer(0.into()),
rmpv::Value::Integer(0.into()),
rmpv::Value::Integer((-1).into()),
rmpv::Value::Boolean(true),
]).await?;
if let rmpv::Value::Array(lines) = result {
let mut text = String::new();
for line in lines {
if let rmpv::Value::String(s) = line {
if let Some(s) = s.as_str() {
text.push_str(s);
text.push('\n');
}
}
return Ok(text);
}
return Ok(text);
}
Err("Invalid response".to_string())
Err("Invalid response format".to_string())
}
async fn get_nvim_cursor() -> Result<String, String> {
use rmpv::Value as RmpValue;
let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);
let req = RmpValue::Array(vec![
RmpValue::Integer(0.into()),
RmpValue::Integer(msgid.into()), // msgid
RmpValue::String("nvim_win_get_cursor".into()),
RmpValue::Array(vec![RmpValue::Integer(0.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]));
}
if let RmpValue::Array(pos) = &arr[3] {
if pos.len() == 2 {
if let (RmpValue::Integer(row), RmpValue::Integer(col)) = (&pos[0], &pos[1]) {
return Ok(format!("Line: {row}, Column: {col}"));
}
let result = call_nvim_method("nvim_win_get_cursor", vec![rmpv::Value::Integer(0.into())]).await?;
if let rmpv::Value::Array(pos) = result {
if pos.len() == 2 {
if let (rmpv::Value::Integer(row), rmpv::Value::Integer(col)) = (&pos[0], &pos[1]) {
return Ok(format!("Line: {row}, Column: {col}"));
}
}
}
Err("Invalid response".to_string())
Err("Invalid response format".to_string())
}
async fn get_nvim_visual_selection() -> Result<String, String> {
@@ -399,30 +382,17 @@ async fn get_nvim_visual_selection() -> Result<String, String> {
end
"#;
use rmpv::Value as RmpValue;
let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);
let req = RmpValue::Array(vec![
RmpValue::Integer(0.into()),
RmpValue::Integer(msgid.into()), // msgid
RmpValue::String("nvim_exec_lua".into()),
RmpValue::Array(vec![
RmpValue::String(lua_script.into()),
RmpValue::Array(vec![]),
]),
]);
let resp = call_nvim(req).await?;
if let RmpValue::Array(arr) = resp {
if !arr[2].is_nil() {
return Err(format!("Neovim error: {:?}", arr[2]));
}
if let RmpValue::String(s) = &arr[3] {
if let Some(text) = s.as_str() {
return Ok(text.to_string());
}
let result = call_nvim_method("nvim_exec_lua", vec![
rmpv::Value::String(lua_script.into()),
rmpv::Value::Array(vec![]),
]).await?;
if let rmpv::Value::String(s) = result {
if let Some(text) = s.as_str() {
return Ok(text.to_string());
}
}
Err("Invalid response".to_string())
Err("Invalid response format".to_string())
}
async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
@@ -440,26 +410,12 @@ async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
"#
);
use rmpv::Value as RmpValue;
let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);
let req = RmpValue::Array(vec![
RmpValue::Integer(0.into()),
RmpValue::Integer(msgid.into()), // msgid
RmpValue::String("nvim_exec_lua".into()),
RmpValue::Array(vec![
RmpValue::String(lua_script.into()),
RmpValue::Array(vec![]),
]),
]);
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())
call_nvim_method("nvim_exec_lua", vec![
rmpv::Value::String(lua_script.into()),
rmpv::Value::Array(vec![]),
]).await?;
Ok(())
}
fn rmpv_to_json(val: &rmpv::Value) -> serde_json::Value {
@@ -505,26 +461,11 @@ fn rmpv_to_json(val: &rmpv::Value) -> serde_json::Value {
}
async fn execute_nvim_lua(code: &str) -> Result<String, String> {
use rmpv::Value as RmpValue;
let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);
let req = RmpValue::Array(vec![
RmpValue::Integer(0.into()),
RmpValue::Integer(msgid.into()), // msgid
RmpValue::String("nvim_exec_lua".into()),
RmpValue::Array(vec![RmpValue::String(code.into()), RmpValue::Array(vec![])]),
]);
let resp = call_nvim(req).await?;
if let RmpValue::Array(arr) = resp {
if !arr[2].is_nil() {
return Err(format!("Neovim error: {:?}", arr[2]));
}
if arr.len() > 3 {
return Ok(serde_json::to_string_pretty(&rmpv_to_json(&arr[3])).unwrap_or_default());
}
return Ok(String::new());
}
Err("Invalid response".to_string())
let result = call_nvim_method("nvim_exec_lua", vec![
rmpv::Value::String(code.into()),
rmpv::Value::Array(vec![]),
]).await?;
Ok(serde_json::to_string_pretty(&rmpv_to_json(&result)).unwrap_or_default())
}
macro_rules! send_text_result {