diff --git a/nvim-core/src/lib.rs b/nvim-core/src/lib.rs index a925f1b..5530f3f 100644 --- a/nvim-core/src/lib.rs +++ b/nvim-core/src/lib.rs @@ -999,6 +999,31 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { "type": "object", "properties": {} } + }, + { + "name": "nvim_read_file", + "description": "Read specific lines from a file buffer (like cat or sed -n). Reads unsaved changes if open.", + "inputSchema": { + "type": "object", + "properties": { + "file": { "type": "string" }, + "start_line": { "type": "integer", "description": "1-indexed start line (optional, defaults to 1)" }, + "end_line": { "type": "integer", "description": "1-indexed end line (optional, defaults to end of file)" } + }, + "required": ["file"] + } + }, + { + "name": "nvim_search_file", + "description": "Search for a regex pattern within a file buffer (like grep). Searches unsaved changes if open.", + "inputSchema": { + "type": "object", + "properties": { + "file": { "type": "string" }, + "pattern": { "type": "string", "description": "Lua-compatible regex pattern to search for" } + }, + "required": ["file", "pattern"] + } } ] })), @@ -1329,6 +1354,72 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) { Err(e) => send_error(id, -32603, &e).await, } } + + "nvim_read_file" => { + if let Some(file) = args.get("file").and_then(|v| v.as_str()) { + let start_line = args.get("start_line").and_then(|v| v.as_i64()).unwrap_or(1); + let end_line = args.get("end_line").and_then(|v| v.as_i64()).unwrap_or(-1); + + let escaped_file = file.replace('\\', "\\\\").replace('"', "\\\""); + let code = format!(" + local file = \"{escaped_file}\" + local start_idx = {start_line} - 1 + local end_idx = {end_line} + local bufnr = vim.fn.bufnr(file, true) + if vim.fn.bufloaded(bufnr) == 0 then + vim.fn.bufload(bufnr) + end + local lines = vim.api.nvim_buf_get_lines(bufnr, start_idx, end_idx, false) + local res = {{}} + for i, line in ipairs(lines) do + local line_num = start_idx + i + table.insert(res, tostring(line_num) .. ': ' .. line) + end + return table.concat(res, '\\n') + "); + match execute_nvim_lua(&code).await { + Ok(res) => send_text_result!(id.clone(), res), + Err(e) => send_error(id, -32603, &e).await, + } + } else { + send_error(id, -32602, "Missing 'file'").await; + } + } + + "nvim_search_file" => { + if let (Some(file), Some(pattern)) = ( + args.get("file").and_then(|v| v.as_str()), + args.get("pattern").and_then(|v| v.as_str()), + ) { + let escaped_file = file.replace('\\', "\\\\").replace('"', "\\\""); + let escaped_pattern = pattern.replace('\\', "\\\\").replace('"', "\\\""); + let code = format!(" + local file = \"{escaped_file}\" + local pattern = \"{escaped_pattern}\" + local bufnr = vim.fn.bufnr(file, true) + if vim.fn.bufloaded(bufnr) == 0 then + vim.fn.bufload(bufnr) + end + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local matches = {{}} + for i, line in ipairs(lines) do + if string.match(line, pattern) then + table.insert(matches, tostring(i) .. ': ' .. line) + end + end + if #matches == 0 then + return 'No matches found.' + end + return table.concat(matches, '\\n') + "); + match execute_nvim_lua(&code).await { + Ok(res) => send_text_result!(id.clone(), res), + Err(e) => send_error(id, -32603, &e).await, + } + } else { + send_error(id, -32602, "Missing 'file' or 'pattern'").await; + } + } "nvim_execute_lua" => { if let Some(code) = args.get("code").and_then(|v| v.as_str()) {