IA en Neovim: las tres capas
Integra IA sin caos: una herramienta por capa. Completado inline por LSP nativo, chat y edición multi-línea con CodeCompanion, y agentes de terminal dentro del editor con sidekick.nvim.
La IA potencia Neovim, pero mal integrada es un caos de plugins peleándose por las mismas teclas. La forma sana de pensarlo es en capas, con exactamente una herramienta por capa. Y hay una novedad que cambia la capa de abajo: desde 0.12 el completado inline forma parte del protocolo LSP, así que buena parte de lo que antes era plugin ahora es cliente.
- Entender las tres capas de IA y elegir una por capa.
- Completado inline nativo con
vim.lsp.inline_completion. - Chat y edición multi-línea con CodeCompanion.
- Agentes de terminal dentro de Neovim con sidekick.nvim, sin robar
<Tab>.
Las tres capas
flowchart TD A[Capa 1 - completado inline mientras escribes] --> D[Tu edicion] B[Capa 2 - chat y edicion multilinea] --> D C[Capa 3 - agentes de terminal sobre el proyecto] --> D style A fill:#a6e3a1,color:#11111b style B fill:#89b4fa,color:#11111b style C fill:#cba6f7,color:#11111b style D fill:#fab387,color:#11111b
No instales dos plugins de completado inline a la vez: pelearán por la misma tecla y por el mismo espacio visual en pantalla. Elige uno por capa y el flujo se mantiene limpio.
Capa 1 — Completado inline
Sugerencias largas que aparecen como texto superpuesto y aceptas con una tecla. Desde Neovim 0.12 esto tiene nombre en el protocolo, textDocument/inlineCompletion, y módulo en el cliente: vim.lsp.inline_completion. Si tu proveedor publica un servidor de lenguaje, no necesitas un plugin para esta capa.
El manual documenta el arranque con Copilot. Primero el servidor:
npm install --global @github/copilot-language-server
Luego la config, en un archivo lsp/copilot.lua de tu runtimepath o directamente con vim.lsp.config:
vim.lsp.config("copilot", {
cmd = { "copilot-language-server", "--stdio" },
root_markers = { ".git" },
init_options = {
editorInfo = { name = "Neovim", version = tostring(vim.version()) },
editorPluginInfo = { name = "Neovim", version = tostring(vim.version()) },
},
})
vim.lsp.enable("copilot")
-- Las sugerencias se refrescan solas mientras estás en Insert.
vim.lsp.inline_completion.enable()
-- get() aplica la sugerencia y devuelve false si no hay ninguna:
-- eso hace que el fallback a Tab sea limpio.
vim.keymap.set("i", "<Tab>", function()
if not vim.lsp.inline_completion.get() then
return "<Tab>"
end
end, { expr = true, desc = "Aceptar sugerencia inline" })
-- Si el servidor ofrece varias, se recorren con select().
vim.keymap.set("i", "<M-]>", function() vim.lsp.inline_completion.select({ count = 1 }) end)
vim.keymap.set("i", "<M-[>", function() vim.lsp.inline_completion.select({ count = -1 }) end)
Falta autenticarse. nvim-lspconfig incluye tanto el lsp/copilot.lua de referencia como el comando :LspCopilotSignIn.
Ese <Tab> no es casual: es la misma tecla de la lección 2.2, y por eso el ejemplo la encadena en vez de reclamarla. <C-Space> dispara el completado y <Tab> navega el menú; ninguna herramienta de IA puede quedárselas para ella sola. Si dos plugins mapean <Tab> por su cuenta, gana el que cargue último, cambia entre arranques, y el síntoma es que el completado deja de responder sin ningún error. Diagnóstico: :verbose imap <Tab> y :map <C-Space>.
Plugins de esta capa
El completado inline por plugin sigue existiendo y es una alternativa perfectamente válida, sobre todo si ya lo tienes montado. copilot.lua es el más usado. Sus teclas por defecto viven en Meta, precisamente para no chocar con las del completado:
return {
"zbirenbaum/copilot.lua",
cmd = "Copilot",
event = "InsertEnter",
opts = {
suggestion = {
auto_trigger = true,
keymap = {
accept = "<M-l>",
next = "<M-]>",
prev = "<M-[>",
dismiss = "<C-]>",
},
},
panel = { enabled = false },
},
}vim.pack.add({ { src = "https://github.com/zbirenbaum/copilot.lua" } })
require("copilot").setup({
suggestion = {
auto_trigger = true,
keymap = {
accept = "<M-l>",
next = "<M-]>",
prev = "<M-[>",
dismiss = "<C-]>",
},
},
panel = { enabled = false },
})Estas son las teclas por defecto del plugin. Si las cambias, no las muevas a Tab ni a Ctrl-Espacio.
Existen otras opciones en esta capa —Supermaven, Minuet, NeoCodeium entre ellas—, con distintos compromisos entre latencia, proveedor y privacidad. No las cubrimos aquí: si te interesa alguna, la regla sigue siendo la misma, una sola en esta capa y sus teclas fuera de <Tab> y <C-Space>.
Todo lo de esta capa manda contexto de tu buffer a un servicio remoto salvo que el modelo corra en tu máquina. Algunos plugins de completado inline admiten backends locales; si esa es tu restricción, verifícalo en la documentación del plugin concreto antes de montarlo, porque cambia rápido.
Capa 2 — Edición multi-línea y chat
Para “reescribe esta función”, “explícame este error” o refactors de un bloque entero. CodeCompanion es la opción estable y bien documentada; Avante es la alternativa de estilo Cursor con diffs.
return {
"olimorris/codecompanion.nvim",
dependencies = { "nvim-lua/plenary.nvim", "nvim-treesitter/nvim-treesitter" },
opts = {
strategies = {
chat = { adapter = "anthropic" },
inline = { adapter = "anthropic" },
},
},
keys = {
{ "<leader>ac", "<cmd>CodeCompanionChat Toggle<cr>", mode = { "n", "v" }, desc = "IA: chat" },
{ "<leader>ai", "<cmd>CodeCompanion<cr>", mode = { "n", "v" }, desc = "IA: edición inline" },
{ "<leader>aA", "<cmd>CodeCompanionActions<cr>", mode = { "n", "v" }, desc = "IA: paleta de acciones" },
},
}vim.pack.add({
{ src = "https://github.com/nvim-lua/plenary.nvim" },
{ src = "https://github.com/olimorris/codecompanion.nvim" },
})
require("codecompanion").setup({
strategies = {
chat = { adapter = "anthropic" },
inline = { adapter = "anthropic" },
},
})
vim.keymap.set({ "n", "v" }, "<leader>ac", "<cmd>CodeCompanionChat Toggle<cr>", { desc = "IA: chat" })
vim.keymap.set({ "n", "v" }, "<leader>ai", "<cmd>CodeCompanion<cr>", { desc = "IA: edición inline" })
vim.keymap.set({ "n", "v" }, "<leader>aA", "<cmd>CodeCompanionActions<cr>", { desc = "IA: paleta de acciones" })Exporta la clave de tu proveedor como variable de entorno antes de arrancar Neovim.
CodeCompanion soporta varios adaptadores (Anthropic, OpenAI, Copilot, Gemini, Ollama, xAI). Exporta tu clave como variable de entorno —por ejemplo ANTHROPIC_API_KEY— antes de arrancar Neovim, o reutiliza tu suscripción de Copilot como backend. Los nombres exactos de modelo cambian a menudo: cógelos de la documentación del adaptador, no de un tutorial.
Flujo típico: selecciona código en modo Visual, <leader>ai, describe el cambio (“añade manejo de errores”) y revisa el diff antes de aceptarlo. Para preguntas abiertas, <leader>ac abre un chat con el contexto de tu buffer, y <leader>aA abre la paleta de acciones con la biblioteca de prompts.
Capa 3 — Agentes de terminal dentro de Neovim
Los agentes de línea de comandos trabajan sobre todo el proyecto: leen y editan varios archivos y ejecutan comandos. sidekick.nvim hace dos cosas distintas y conviene no confundirlas:
Next Edit Suggestions
Sugerencias de refactor multi-línea que vienen del servidor LSP de Copilot, con diffs enriquecidos y navegación hunk a hunk. Requiere suscripción de Copilot.
Terminal de agentes
Un panel con el agente que elijas —Claude, Codex, Gemini, opencode y otros—, con envío de contexto (selección, archivo, diagnósticos) y sesiones persistentes vía tmux o zellij. No requiere Copilot.
Necesita Neovim 0.11.2 o superior y el copilot-language-server activado con vim.lsp.enable, es decir, exactamente la config de la capa 1.
return {
"folke/sidekick.nvim",
opts = {
cli = {
mux = { backend = "zellij", enabled = true }, -- o "tmux"
},
},
keys = {
{ "<leader>aa", function() require("sidekick.cli").toggle() end, desc = "Sidekick: abrir agente" },
{ "<leader>as", function() require("sidekick.cli").select() end, desc = "Sidekick: elegir agente" },
{ "<leader>ap", function() require("sidekick.cli").prompt() end, mode = { "n", "x" }, desc = "Sidekick: prompt con contexto" },
{ "<leader>av", function() require("sidekick.cli").send({ msg = "{selection}" }) end, mode = "x", desc = "Sidekick: enviar selección" },
},
}vim.pack.add({ { src = "https://github.com/folke/sidekick.nvim" } })
require("sidekick").setup({
cli = {
mux = { backend = "zellij", enabled = true },
},
})
vim.keymap.set("n", "<leader>aa", function() require("sidekick.cli").toggle() end, { desc = "Sidekick: abrir agente" })
vim.keymap.set("n", "<leader>as", function() require("sidekick.cli").select() end, { desc = "Sidekick: elegir agente" })
vim.keymap.set({ "n", "x" }, "<leader>ap", function() require("sidekick.cli").prompt() end, { desc = "Sidekick: prompt con contexto" })
vim.keymap.set("x", "<leader>av", function() require("sidekick.cli").send({ msg = "{selection}" }) end, { desc = "Sidekick: enviar selección" })Tras instalar, ejecuta :checkhealth sidekick. Detecta qué agentes tienes disponibles y si el LSP de Copilot está adjunto.
Integra los agentes de terminal y las Next Edit Suggestions del servidor LSP de Copilot. El puente entre los agentes de línea de comandos y tu editor.
El <Tab> de sidekick, hecho bien
La documentación de sidekick propone <Tab> para saltar a la siguiente sugerencia o aplicarla. Si lo mapeas por tu cuenta, chocas con el completado. La forma correcta es declararlo dentro de la capa de completado, como una cadena de intentos: cada función devuelve algo solo si le corresponde actuar, y si no, pasa el turno.
opts = {
keymap = {
["<Tab>"] = {
"snippet_forward",
function() return require("sidekick").nes_jump_or_apply() end,
function() return vim.lsp.inline_completion.get() end,
"fallback",
},
},
},
Se lee de arriba abajo: si hay un hueco de snippet, salta; si no, si hay una Next Edit Suggestion, va a ella o la aplica; si no, si hay una sugerencia inline del LSP, la acepta; y si no hay nada de eso, <Tab> hace lo que hace siempre. Un solo dueño de la tecla, un orden explícito, y ningún conflicto que dependa del orden de carga de los plugins.
Si no usas blink.cmp, la misma cadena se escribe a mano sobre el mapeo nativo de <Tab> de la lección 2.2: la lógica es idéntica, solo cambia dónde vive.
El nivel dios con IA no es aceptar todo lo que sugiere: es dirigirla. Completado inline para lo trivial, edición inline para refactors acotados que revisas, y el agente para tareas grandes que supervisas. Tú sigues siendo quien entiende el código y toma las decisiones; la IA elimina el tecleo mecánico. Quien delega el criterio pierde; quien delega el trabajo repetitivo, vuela.
Fíjate además en lo que ha cambiado en esta lección: media capa 1 ha dejado de ser un plugin para ser una llamada del cliente LSP. Cuando una función se estandariza en el protocolo, deja de ser un producto y pasa a ser infraestructura. Revisa tu stack de IA cada pocos meses con esa pregunta en la mano: ¿esto sigue necesitando un plugin?
- Monta la capa 1 con
vim.lsp.inline_completiony el servidor de Copilot, o con un plugin — pero solo uno. - Comprueba con
:verbose imap <Tab>que la tecla tiene un único dueño y que la cadena está en el orden que quieres. - Selecciona una función y pide a la capa 2 que le añada tests; revisa el diff antes de aceptar.
- Abre el agente con
<leader>aay pídele una tarea multi-archivo pequeña. - Ejecuta
:checkhealth sidekicky:lspy confirma que el LSP de Copilot está adjunto. - Repasa
:map <C-Space>una última vez. Si alguien nuevo aparece ahí, has instalado algo que no leíste.