wandres.dev
DIOS · Terminal · IA · Automatización

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.

⏱ 18 min

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.

🎯 Al terminar esta lección sabrás
  • 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
⚠️
Una herramienta por capa

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.

⚠️
Aquí vuelve la regla de teclas

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:

zbirenbaum/copilot.lua
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>.

💡
Si te preocupa mandar código a la nube

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.

olimorris/codecompanion.nvim
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.

ℹ️
Configura tu proveedor

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.

folke/sidekick.nvim
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.

🤝
sidekick.nvimfolke/sidekick.nvim

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.

ai

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.

La IA como copiloto, no como piloto

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 tu stack de IA
  1. Monta la capa 1 con vim.lsp.inline_completion y el servidor de Copilot, o con un plugin — pero solo uno.
  2. Comprueba con :verbose imap <Tab> que la tecla tiene un único dueño y que la cadena está en el orden que quieres.
  3. Selecciona una función y pide a la capa 2 que le añada tests; revisa el diff antes de aceptar.
  4. Abre el agente con <leader>aa y pídele una tarea multi-archivo pequeña.
  5. Ejecuta :checkhealth sidekick y :lsp y confirma que el LSP de Copilot está adjunto.
  6. Repasa :map <C-Space> una última vez. Si alguien nuevo aparece ahí, has instalado algo que no leíste.