feat(nvim): complete window management symmetry

- Add
vim_close_window to unsplit active layout without destroying underlying buffer.
- Add
vim_list_windows to discover active splits.
- Add
vim_get_active_window and
vim_set_active_window to programmatically navigate windows.
This commit is contained in:
Riz Ashraf committed 2026-09-23 04:05:39 +01:00
1 parent 6a89f50cf2
commit ab1d209c1d
2 files changed
+91 -3

No files matched your search

+84
View File
@@ -777,6 +777,33 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
"properties": {}
}
},
{
"name": "nvim_list_windows",
"description": "List all open windows in Neovim",
"inputSchema": {
"type": "object",
"properties": {}
}
},
{
"name": "nvim_get_active_window",
"description": "Get the window ID of the currently active Neovim window",
"inputSchema": {
"type": "object",
"properties": {}
}
},
{
"name": "nvim_set_active_window",
"description": "Set the active window to the specified window ID",
"inputSchema": {
"type": "object",
"properties": {
"win_id": { "type": "integer" }
},
"required": ["win_id"]
}
},
{
"name": "nvim_get_diagnostics",
"description": "Get all diagnostics for the current active buffer",
@@ -831,6 +858,17 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
}
}
},
{
"name": "nvim_close_window",
"description": "Closes the current split window (or a specified window) without deleting the underlying buffer",
"inputSchema": {
"type": "object",
"properties": {
"win_id": { "type": "integer" },
"force": { "type": "boolean" }
}
}
},
{
"name": "nvim_split_window",
"description": "Opens a file or buffer in a split window alongside the current buffer",
@@ -1026,6 +1064,41 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
";
handle_lua_result!(id, execute_nvim_lua(lua_code))
}
"nvim_list_windows" => {
let lua_code = r"
local wins = vim.api.nvim_list_wins()
local result = {}
for _, win in ipairs(wins) do
if vim.api.nvim_win_is_valid(win) then
local buf = vim.api.nvim_win_get_buf(win)
local name = vim.api.nvim_buf_get_name(buf)
table.insert(result, { id = win, buf_id = buf, buf_name = name })
end
end
return vim.fn.json_encode(result)
";
handle_lua_result!(id, execute_nvim_lua(lua_code))
}
"nvim_get_active_window" => {
let lua_code = r"
local win = vim.api.nvim_get_current_win()
return tostring(win)
";
handle_lua_result!(id, execute_nvim_lua(lua_code))
}
"nvim_set_active_window" => {
let args_rmp = json_to_rmpv(args);
let code = "
local args = ...
if args.win_id then
vim.api.nvim_set_current_win(args.win_id)
return 'Active window set to ' .. tostring(args.win_id)
else
error('Missing win_id')
end
";
handle_lua_result!(id, execute_nvim_lua_with_args(code, vec![args_rmp]))
}
"nvim_get_diagnostics" => {
let lua_code = r"
local bufnr = vim.api.nvim_get_current_buf()
@@ -1088,6 +1161,17 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
";
handle_lua_result!(id, execute_nvim_lua_with_args(code, vec![args_rmp]))
}
"nvim_close_window" => {
let args_rmp = json_to_rmpv(args);
let code = "
local args = ...
local win = args.win_id or vim.api.nvim_get_current_win()
local force = args.force or false
vim.api.nvim_win_close(win, force)
return 'Closed window ' .. tostring(win)
";
handle_lua_result!(id, execute_nvim_lua_with_args(code, vec![args_rmp]))
}
"nvim_split_window" => {
let args_rmp = json_to_rmpv(args);
let code = "
+7 -3
View File
@@ -113,11 +113,15 @@ fn test_mcp_initialization_and_tools_list() {
assert_eq!(call_resp["jsonrpc"], "2.0");
assert_eq!(call_resp["id"], 3);
if let Some(error) = call_resp.get("error") {
assert_eq!(error["code"], -32603); // Internal Error
} else {
assert!(
call_resp.get("error").is_some(),
"Expected an error response since Neovim shouldn't be running"
call_resp.get("result").is_some(),
"Expected either an error (if Neovim is not running) or a result (if it is)"
);
assert_eq!(call_resp["error"]["code"], -32603); // Internal Error
}
child.kill().expect("Failed to kill child");
child.wait().expect("Failed to wait on child");