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:
1 parent
ce88bef45e
commit
f76d8ccce8
23 files changed
+1373
-803
No files matched your search
+70
-129
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user