feat(nvim-core): implement nvim_set_extmark for rich virtual text UI feedback

- Added nvim_set_extmark tool schema and handler
- Implemented lua bridge using vim.api.nvim_buf_set_extmark to dynamically overlay ghost text and highlight groups onto active buffers
This commit is contained in:
Riz Ashraf committed 2026-09-22 22:34:06 +01:00
1 parent ea735003e2
commit 6a89f50cf2
1 file changed
+60
+60
View File
@@ -510,6 +510,31 @@ async fn set_nvim_diagnostics(line: i64, message: &str) -> Result<(), String> {
Ok(())
}
async fn set_nvim_extmark(line: i64, text: &str, hl_group: &str) -> Result<(), String> {
let escaped_text = text.replace('\\', "\\\\").replace('"', "\\\"");
let lua_script = format!(
r#"
local ns = vim.api.nvim_create_namespace("gemini_extmarks")
local opts = {{
virt_text = {{{{"{escaped_text}", "{hl_group}"}}}},
virt_text_pos = "eol",
}}
vim.api.nvim_buf_set_extmark(0, ns, {line} - 1, 0, opts)
"#
);
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 {
match val {
rmpv::Value::Nil => serde_json::Value::Null,
@@ -731,6 +756,19 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
"required": ["line", "message"]
}
},
{
"name": "nvim_set_extmark",
"description": "Add virtual text (ghost text) to a specific line in the buffer to give the user rich UI feedback",
"inputSchema": {
"type": "object",
"properties": {
"line": { "type": "integer" },
"text": { "type": "string" },
"highlight_group": { "type": "string", "description": "Optional. E.g., 'DiagnosticVirtualTextInfo', 'Comment'" }
},
"required": ["line", "text"]
}
},
{
"name": "nvim_list_buffers",
"description": "List all open buffers in Neovim",
@@ -952,6 +990,28 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
send_error(id, -32602, "Missing 'line' or 'message'").await;
}
}
"nvim_set_extmark" => {
if let (Some(line), Some(text)) = (
args.get("line").and_then(serde_json::Value::as_i64),
args.get("text").and_then(|v| v.as_str()),
) {
let hl_group = args
.get("highlight_group")
.and_then(|v| v.as_str())
.unwrap_or("Comment");
match set_nvim_extmark(line, text, hl_group).await {
Ok(()) => {
send_text_result!(
id.clone(),
format!("Successfully set extmark on line {}", line)
);
}
Err(e) => send_error(id, -32603, &e).await,
}
} else {
send_error(id, -32602, "Missing 'line' or 'text'").await;
}
}
"nvim_list_buffers" => {
let lua_code = r"
local bufs = vim.api.nvim_list_bufs()