Depurar autocomandos: listar, entender por qué no dispara y el orden
Interrogar la tabla de suscripciones con :autocmd, :verbose autocmd y nvim_get_autocmds, el catálogo ordenado por frecuencia de las causas por las que una reacción no ocurre, qué NO hacer jamás con :au!, y cómo se resuelve el orden cuando varios escuchan el mismo evento.
Los autocomandos fallan de la peor manera posible: en silencio. No hay excepción, no hay mensaje, simplemente el efecto que esperabas no aparece — y no existe un punto de interrupción donde detenerse, porque el problema es que nadie te llamó. Depurar aquí no consiste en leer un error sino en interrogar a la tabla de suscripciones y reconstruir por qué el emparejamiento no ocurrió. Con las herramientas correctas es un proceso de cinco minutos; sin ellas, una tarde de conjeturas.
- Inspeccionar la tabla con
:autocmd,:verbose autocmdynvim_get_autocmds. - Recorrer el catálogo de causas por las que una reacción no se ejecuta, en orden de frecuencia real.
- Saber qué borra exactamente
:au!y por qué su forma sin grupo es una bomba. - Determinar y controlar el orden entre varios suscriptores del mismo evento.
- Instrumentar el bus para medir coste y aislar al culpable por bisección.
Listar: el comando y su versión programable
:autocmd sin argumentos vuelca la tabla completa, lo cual es inútil por volumen. Su valor está en los filtros — grupo, evento y patrón, cualquiera omitible:
:autocmd BufWritePre " todo lo que escucha ese evento:autocmd dios_formato " todo lo del grupo, sea cual sea el evento:autocmd dios_formato BufWritePre " la interseccion de ambos:autocmd FileType lua " el evento con ese patron concreto:autocmd * <buffer> " lo atado al buffer actual (leccion 9.3):verbose autocmd BufWritePre " y ademas, DESDE DONDE se definio cada uno:verbose autocmd es la herramienta decisiva y la más infrautilizada: añade a cada entrada una línea Last set from con el archivo y la línea donde se registró. Convierte «algo formatea mi buffer y no sé qué» en una respuesta inmediata, y cuando el culpable es un plugin de terceros, es lo único que te dice cuál.
La forma que nunca debes escribir: :au! sin grupo
La exclamación borra. :au! dios_formato vacía tu grupo: bien. Pero :au! BufWritePre borra todos los autocomandos de ese evento, tuyos y de todos tus plugins; y :au! a secas vacía la tabla entera de la sesión — detección de tipos, resaltado, LSP, todo. La propia ayuda lo marca con un aviso: no lo hagas sin grupo. No hay confirmación, no hay deshacer, y el editor sigue funcionando en apariencia mientras todo lo reactivo está muerto hasta que reinicies. Si tu impulso era «limpiar mis autocomandos», la operación que buscas es por grupo — y si no tienen grupo, la lección 9.2 era esta.
Para trabajo serio, la API devuelve datos estructurados en lugar de texto para leer con los ojos:
-- Inventario legible de todo lo que escucha un eventolocal entradas = vim.api.nvim_get_autocmds({ event = "BufWritePre" })for _, a in ipairs(entradas) do print(("[%s] %s -> %s"):format(a.group_name or "sin grupo", a.pattern, a.desc or "sin descripcion"))end-- Cuantos autocomandos ha registrado cada grupo (filtro vacio = toda la tabla)local por_grupo = {}for _, a in ipairs(vim.api.nvim_get_autocmds({})) do local g = a.group_name or "ANONIMO" por_grupo[g] = (por_grupo[g] or 0) + 1endvim.print(por_grupo)Ese segundo fragmento merece ejecutarse en cualquier configuración madura. El recuento bajo la clave ANONIMO es una métrica de deuda técnica: son exactamente los autocomandos que ya no puedes borrar ni identificar.
El campo desc es tu futura sesión de depuración
Un autocomando sin desc aparece en los listados como una función Lua sin nombre y sin contexto. Escribir la descripción cuesta cinco segundos hoy y ahorra media hora dentro de un año, cuando mires una tabla de ochenta entradas intentando averiguar cuál mueve tu cursor.
Por qué no dispara: el catálogo de causas
Cuando una reacción no ocurre, la causa está casi siempre en esta lista, y conviene recorrerla en orden porque está ordenada por frecuencia real.
1 · El patrón no casa
Dominio equivocado: *.lua en FileType, o un patrón con barra contra una ruta que no esperabas (lección 9.3). Imprime args.match con una sonda sin patrón para ver el valor real.
2 · Llegaste tarde
El evento ya ocurrió cuando tu autocomando nació. Clásico con VimEnter, con el primer BufEnter y con todo lo de carga perezosa. Es el suscriptor tardío de la lección 9.4, ahora en tu contra.
3 · Alguien limpió tu grupo
Otro archivo creó un grupo del mismo nombre con clear = true después que tú. Sin prefijo propio, la colisión es cuestión de tiempo (lección 9.2).
4 · Los eventos estaban suprimidos
La opción 'eventignore', el modificador :noautocmd, o la ausencia de nested cuando el disparo venía de dentro de otro callback (lección 9.3). El evento no se emitió: no es que no lo escucharas.
Hay dos causas más que no caben en tarjetas y producen los casos más desconcertantes. La primera: el buffer no tiene nombre. Muchos eventos comparan el patrón contra una ruta vacía, de modo que cualquier patrón distinto del asterisco falla en buffers recién creados, en los del terminal y en los de plugins de interfaz. La segunda: el autocomando sí disparó, pero su callback no hizo nada visible — una condición interna que resultó falsa, un pcall que se tragó el error. La tabla te dice que todo está correcto porque todo está correcto; el silencio vive dentro de tu función. Antes de dudar del emparejamiento, pon un vim.notify en la primera línea del callback.
-- Sonda universal: que llega realmente y con que valoresvim.api.nvim_create_autocmd( { "BufReadPost", "BufWritePre", "FileType", "LspAttach", "User" }, { group = vim.api.nvim_create_augroup("dios_sonda", { clear = true }), desc = "Traza de diagnostico, borrar cuando termines", callback = function(args) vim.notify(("%s | match=%s | buf=%d"):format(args.event, args.match, args.buf)) end, })Y para la hipótesis inversa —«¿es un autocomando lo que me rompe el guardado?»— existe la prueba de descarte perfecta: :noautocmd w escribe el archivo con el bus silenciado. Si así funciona, el culpable está en BufWritePre; :verbose autocmd BufWritePre te da su nombre y su archivo.
El orden de ejecución
Cuando varios autocomandos escuchan el mismo evento y todos casan, se ejecutan en orden de registro. No hay prioridades, no hay pesos, y los grupos no reordenan nada: un autocomando registrado después corre después, pertenezca al grupo que pertenezca.
Cuando el orden importa de verdad hay tres soluciones legítimas, en orden de preferencia. La primera y mejor: no depender del orden, haciendo cada autocomando independiente del efecto de los demás. La segunda: fusionar en uno, un único suscriptor que ejecuta las acciones en la secuencia que quieres — el orden pasa de ser emergente a ser código explícito. La tercera, para cuando no controlas al otro: reregistrar para ir al final, borrando tu grupo y recreándolo en un momento posterior, típicamente desde VeryLazy o desde un VimEnter diferido.
-- Orden explicito: un suscriptor que secuencia, en vez de dos que compitenvim.api.nvim_create_autocmd("BufWritePre", { group = vim.api.nvim_create_augroup("dios_pipeline_guardado", { clear = true }), desc = "Saneado y formato en secuencia determinista", callback = function(args) require("dios.saneado").quitar_espacios(args.buf) require("dios.formato").aplicar(args.buf) end,})Instrumentar: medir y aislar
Cuando el problema no es que no dispare sino que dispara demasiado o cuesta demasiado, la técnica cambia: hay que medir. Los eventos de alta frecuencia —CursorMoved, TextChanged, BufEnter— son los sospechosos habituales de un editor perezoso.
-- Cuenta disparos por evento durante una sesion de trabajolocal cuentas = {}vim.api.nvim_create_autocmd( { "CursorMoved", "CursorMovedI", "TextChanged", "BufEnter", "InsertLeave" }, { group = vim.api.nvim_create_augroup("dios_metricas", { clear = true }), callback = function(args) cuentas[args.event] = (cuentas[args.event] or 0) + 1 end, })vim.api.nvim_create_user_command("Disparos", function() vim.print(cuentas) end, {})Un minuto de edición normal y :Disparos te enseña cientos de CursorMoved. Cada milisegundo de callback colgado ahí se multiplica por ese número — es la aritmética que en el Nivel 12 resolverás con debounce y vim.schedule, pero la mitad de las veces la respuesta correcta es más simple: ese trabajo no debía estar en CursorMoved.
Para aislar una lentitud concreta, la bisección sobre grupos es infalible y rápida: borra la mitad de tus grupos con nvim_del_augroup_by_name, comprueba si el síntoma persiste, y repite sobre la mitad culpable. Como tus archivos son idempotentes (lección 9.2), restaurar es volver a evaluarlos. Si el síntoma sobrevive a borrar todos tus grupos, el origen es un plugin, y :verbose autocmd te da su archivo en una línea.
Un sistema de eventos sin observabilidad es indistinguible de uno roto
La lección que este nivel deja más allá de Neovim es que el desacoplamiento no es gratis: se paga en observabilidad, y hay que reponerla deliberadamente. Una llamada directa se depura leyendo código, porque la relación entre quien llama y quien es llamado está escrita en el texto fuente y cualquier búsqueda la encuentra. Una suscripción a un evento no está escrita en ninguna parte que puedas leer: existe solo en tiempo de ejecución, dentro de una tabla que se construye por acumulación durante el arranque, y su contenido depende de qué plugins se cargaron, en qué orden y bajo qué condiciones. Has cambiado un grafo estático y verificable por uno dinámico e invisible, y ese cambio es el mismo que separa una llamada a función de un mensaje por cola, un import de una inyección de dependencias, un monolito de una arquitectura de microservicios. En los tres casos la respuesta profesional es idéntica, y no es renunciar al desacoplamiento: es invertir en instrumentación proporcional al acoplamiento eliminado — nombres descriptivos en cada suscripción, espacios de nombres que permitan filtrar, capacidad de enumerar el grafo en caliente, trazas de qué se emitió y quién reaccionó, métricas de cuánto costó. Neovim te da todas las piezas: desc, los grupos, nvim_get_autocmds, :verbose autocmd. La diferencia entre una configuración mantenible durante años y una que abandonarás cuando se vuelva incomprensible no está en qué autocomandos escribiste, sino en si puedes responder en treinta segundos a la pregunta de quién reacciona a qué. Escribir el evento es la parte fácil. Poder verlo funcionando es la ingeniería.
La línea Last set from señala archivo y línea de cada entrada, tuya o de un plugin. Ahí estará tu ultima_posicion de la 2.1 — y quien más ande tocando.
Escribe con el bus silenciado. Si así funciona, el culpable escucha BufWritePre; si sigue roto, deja de sospechar de los autocomandos.
Cada uno es una entrada que ya no puedes borrar ni identificar. Si el número te incomoda, la lección 9.2 sabe qué hacer con él.
Emite el evento a mano con el match que quieras: si tu sonda no salta con el disparo sintético, el problema es el emparejamiento, no el momento.
Audita tu propio bus
- Ejecuta el recuento por grupo con
nvim_get_autocmds({})y calcula cuántos autocomandos anónimos arrastra tu configuración con todos los plugins cargados. - Usa
:verbose autocmd BufWritePree identifica el archivo exacto del que sale cada entrada, tuya o de un plugin. - Instala la sonda universal, provoca un fallo de patrón deliberado en
FileType(el*.luade la lección 9.3) y diagnostícalo leyendoargs.match. - Mide con el contador cuántas veces dispara
CursorMoveden un minuto de edición normal, y razona qué presupuesto de tiempo le deja eso a cualquier callback colgado ahí. - Aísla por bisección de grupos una lentitud al guardar (puedes fabricarla: un
vim.wait(300)escondido en un grupo) y reescribe los dos autocomandos implicados como un único pipeline con orden explícito.