Grupos: idempotencia y arquitectura de tu configuración
Por qué un autocomando sin grupo es una fuga de memoria conceptual, qué hace exactamente clear en nvim_create_augroup, cómo la idempotencia convierte una recarga en una operación segura y cómo organizar toda tu configuración por espacios de nombres.
Hay un síntoma que delata a toda configuración de Neovim que no ha alcanzado este nivel: recargas la config, guardas un archivo y el formateador corre dos veces. Luego tres. Luego el editor se congela un segundo entero al guardar y nadie sabe por qué. No es un fallo de Neovim ni del plugin: es la ausencia de grupos. Un autocomando sin grupo es un registro que entra en la tabla y del que nadie conserva la llave.
- Explicar por qué registrar un autocomando dos veces lo ejecuta dos veces.
- Dominar la semántica exacta de
clearennvim_create_augroup. - Convertir la recarga de tu configuración en una operación idempotente.
- Organizar la configuración completa en espacios de nombres coherentes.
El problema: la configuración que se recarga
vim.api.nvim_create_autocmd siempre añade. Nunca sustituye, nunca deduplica, nunca comprueba si ya existe algo equivalente. Cada llamada crea una entrada nueva con su propio identificador. Si el archivo que contiene esa llamada se evalúa dos veces —porque recargaste la configuración, porque un plugin se cargó tarde, porque estás iterando sobre tu propio código— tendrás dos autocomandos idénticos escuchando el mismo evento, y ambos se ejecutarán.
flowchart TD A[Se evalua tu archivo de autocomandos] --> B[Hay grupo con clear] B -->|No| C[Se anade una entrada mas] C --> D[Tabla con N copias del mismo autocomando] D --> E[El efecto se aplica N veces] B -->|Si| F[Se borran las entradas previas del grupo] F --> G[Se registra una sola entrada] G --> H[Tabla estable tras cualquier numero de recargas] style D fill:#f38ba8,color:#11111b style H fill:#a6e3a1,color:#11111b
La gravedad del problema depende de si la acción es idempotente por sí misma. Un autocomando que ajusta una opción puede ejecutarse cien veces sin consecuencias visibles. Uno que formatea el buffer, que inserta una cabecera de licencia o que envía una notificación produce efectos acumulativos, y ahí la duplicación deja de ser un detalle de higiene para convertirse en corrupción de datos.
Anatomía de nvim_create_augroup
Un grupo es un espacio de nombres con identidad propia. Se crea con un nombre y una tabla de opciones, y devuelve un identificador numérico que luego pasas a cada autocomando.
local grupo = vim.api.nvim_create_augroup("dios_formato", { clear = true })
vim.api.nvim_create_autocmd("BufWritePre", {
group = grupo,
pattern = { "*.lua", "*.rs" },
desc = "Formatea antes de escribir al disco",
callback = function(args)
vim.lsp.buf.format({ bufnr = args.buf, timeout_ms = 2000 })
end,
})
La semántica de clear merece precisión quirúrgica, porque su nombre engaña. Con clear = true, Neovim crea el grupo si no existe y, si ya existía, borra todos los autocomandos que contenía antes de devolverte el identificador. Con clear = false, el grupo se crea si falta y, si ya estaba, te devuelve su identificador intacto con todo su contenido.
De esa asimetría sale la disciplina completa del nivel. La forma clear = true en la cabecera del archivo hace que el archivo entero sea idempotente: da igual cuántas veces se evalúe, el estado final de la tabla es siempre el mismo. Es exactamente el patrón de una migración declarativa: no describes un incremento, describes el estado deseado y borras lo anterior. La forma clear = false sirve para el caso opuesto, cuando varios archivos contribuyen autocomandos al mismo grupo y ninguno debe pisar a los demás; es potente, pero devuelve el problema de la duplicación y solo debe usarse con plena conciencia.
Los autocomandos anónimos también pueden borrarse, pero solo por identificador individual y solo si lo guardaste. Si perdiste la referencia —y al recargar la configuración la pierdes siempre— ese autocomando queda vivo hasta que cierres Neovim, invisible a cualquier limpieza por nombre. No hay comando que diga “borra los que registré yo”. El grupo no es una comodidad organizativa: es el único asidero que tienes sobre tu propio código.
Un grupo por intención: arquitectura de tu configuración
El siguiente paso es dejar de crear grupos ad hoc y adoptar una convención. La forma canónica, que verás en LazyVim y en casi toda configuración seria, es una función auxiliar que aplica un prefijo y clear = true de manera uniforme.
-- lua/config/autocmds.lua
local function augroup(nombre)
return vim.api.nvim_create_augroup("dios_" .. nombre, { clear = true })
end
-- Restaurar la posicion del cursor al reabrir un archivo
vim.api.nvim_create_autocmd("BufReadPost", {
group = augroup("ultima_posicion"),
desc = "Vuelve a la ultima linea editada",
callback = function(args)
local marca = vim.api.nvim_buf_get_mark(args.buf, '"')
if marca[1] > 0 and marca[1] <= vim.api.nvim_buf_line_count(args.buf) then
pcall(vim.api.nvim_win_set_cursor, 0, marca)
end
end,
})
-- Cerrar buffers auxiliares con q, solo donde tiene sentido
vim.api.nvim_create_autocmd("FileType", {
group = augroup("cierre_rapido"),
pattern = { "help", "qf", "man", "checkhealth", "lspinfo" },
desc = "Mapea q para cerrar ventanas auxiliares",
callback = function(args)
vim.bo[args.buf].buflisted = false
vim.keymap.set("n", "q", "<cmd>close<cr>", { buffer = args.buf, silent = true })
end,
})
El prefijo cumple una función real y no cosmética. La tabla de grupos es global y compartida con todos los plugins instalados: si creas un grupo llamado lsp con clear = true y algún plugin ya usaba ese nombre, acabas de destruir sus autocomandos sin ningún aviso, y el fallo resultante aparecerá en un lugar que no tiene relación aparente con tu cambio. Un prefijo estable —el nombre de tu configuración— hace imposible esa colisión.
La granularidad correcta es un grupo por intención, no uno por archivo ni uno para todo. Cada grupo debería poder describirse en una frase: restaurar posición, formato al guardar, ajustes por lenguaje, integración con el terminal. Esa granularidad es la que permite después desactivar una funcionalidad completa borrando un grupo, o inspeccionar exactamente qué escucha cada evento sin leer código.
Borrar, inspeccionar y desmontar
Los grupos habilitan operaciones que sobre autocomandos anónimos son sencillamente imposibles.
Vaciar sin destruir
vim.api.nvim_clear_autocmds con la clave group elimina todos los autocomandos del grupo pero conserva el grupo existente y su identificador.
Filtrar el borrado
La misma llamada admite event, pattern y buffer, de modo que puedes retirar quirúrgicamente un subconjunto sin tocar el resto del grupo.
Destruir el grupo
vim.api.nvim_del_augroup_by_name borra grupo y contenido de una vez. Es lo que necesitas para desactivar una funcionalidad entera en caliente.
Inspeccionar
vim.api.nvim_get_autocmds acepta el filtro group y devuelve una tabla estructurada con cada entrada, su patrón y su descripción.
-- Un interruptor real: activar y desactivar formato al guardar en caliente
local NOMBRE = "dios_formato_al_guardar"
local function activar()
local g = vim.api.nvim_create_augroup(NOMBRE, { clear = true })
vim.api.nvim_create_autocmd("BufWritePre", {
group = g,
desc = "Formatea antes de escribir",
callback = function(args)
vim.lsp.buf.format({ bufnr = args.buf })
end,
})
end
local function desactivar()
pcall(vim.api.nvim_del_augroup_by_name, NOMBRE)
end
vim.api.nvim_create_user_command("FormatoAlGuardar", function(opts)
if opts.args == "off" then desactivar() else activar() end
end, { nargs = 1, complete = function() return { "on", "off" } end })
Ese comando es imposible de escribir sin grupos. Con ellos son doce líneas y funciona sin reiniciar.
Un script se ejecuta una vez y describe una secuencia de acciones; una configuración se ejecuta un número impredecible de veces y debe describir un estado. Ese es el abismo real que cruza clear = true, y es la misma frontera que separa un instalador artesanal de una herramienta de gestión declarativa, o un conjunto de comandos sueltos de una migración reversible. Cuando escribes nvim_create_augroup con clear estás afirmando algo mucho más fuerte que “voy a agrupar unas cuantas cosas”: estás declarando que este bloque de código posee en exclusiva una región del espacio de estados del editor, y que evaluarlo lleva esa región a un valor conocido con independencia de dónde estuviera antes. Esa es literalmente la definición matemática de idempotencia, y es lo que permite que recargar la configuración sea una operación segura en vez de una apuesta. Sin ella, tu configuración acumula historia: cada recarga deja sedimentos que nadie puede enumerar y que solo se limpian cerrando el proceso, y el editor se vuelve un sistema cuyo comportamiento depende de por cuántos estados intermedios ha pasado. Con ella, el estado del editor es una función pura de tu código fuente. Todo lo que hace agradable trabajar sobre la propia configuración —recargar sin miedo, iterar rápido, desactivar una función y recuperarla, razonar sobre qué está activo— descansa sobre esa única propiedad. Los grupos no son carpetas: son la garantía de que tu editor no tiene memoria oculta.
- Registra el mismo autocomando de notificación sin grupo, evalúa el archivo tres veces y cuenta cuántas notificaciones recibes al disparar el evento.
- Repite el experimento con un grupo creado con
clear = truey explica por qué el resultado no cambia con el número de recargas. - Escribe la función auxiliar con prefijo y migra todos los autocomandos de tu configuración a grupos con una intención por grupo.
- Implementa un comando de usuario que active y desactive un grupo completo en caliente sin reiniciar Neovim.
- Usa
nvim_get_autocmdsfiltrando por tu prefijo y genera un inventario legible de todo lo que tu configuración escucha.