Introspección: descubrir la API desde dentro
Neovim se describe a sí mismo en tiempo de ejecución: el catálogo completo de opciones con nvim_get_all_options_info, la metadata versionada de la API, el motor de completado como función con getcompletion, y :Inspect para saber qué hay exactamente bajo el cursor. Interrogar al editor en vez de memorizarlo, y saber cuándo lo descubierto es contrato y cuándo casualidad.
Las cuatro lecciones anteriores se pueden resumir en un solo cambio de hábito: dejar de tratar a Neovim como un conjunto de recetas que hay que recordar y empezar a tratarlo como un sistema que responde preguntas sobre sí mismo. El editor sabe cuántas opciones tiene, de qué tipo es cada una, cuál es su ámbito y cuáles has modificado tú. Sabe qué funciones expone su API, con qué parámetros, desde qué nivel y cuáles están obsoletas. Sabe qué mapeos hay activos ahora mismo y quién los definió, y hasta qué capa de resaltado está pintando el carácter bajo tu cursor. Todo eso es consultable desde Lua en una línea, y quien adquiere el reflejo de preguntar en lugar de buscar deja de depender de la memoria para siempre.
- Consultar el catálogo completo de opciones y filtrarlo por ámbito, tipo o modificación.
- Leer la especificación de la API en tiempo de ejecución, incluidos niveles y obsolescencia.
- Convertir el motor de completado de la línea de comandos en una función de consulta.
- Interrogar lo que hay bajo el cursor con
:Inspectyvim.inspect_pos. - Combinar introspección y documentación con criterio sobre qué garantiza cada una.
El editor sabe describirse a sí mismo
El punto de entrada es más simple de lo que parece: en Lua todo son tablas, incluida vim.api, así que puedes recorrerla. Y para imprimir tablas de forma legible existe vim.print, junto con la abreviatura :lua= que evita hasta escribirla:
-- Estas formas imprimen lo mismovim.print(vim.api.nvim_get_mode())-- :lua vim.print(vim.api.nvim_get_mode())-- :lua= vim.api.nvim_get_mode()-- Cuantas funciones expone la API y cuales hablan de bufferslocal nombres = vim.tbl_keys(vim.api)print(#nombres)local de_buffer = vim.iter(nombres) :filter(function(n) return n:match("^nvim_buf_") end) :totable()table.sort(de_buffer)vim.print(de_buffer):lua= es probablemente el atajo con mejor relación entre esfuerzo y beneficio de todo Neovim: evalúa la expresión y la imprime formateada, sin envoltorios —y vim.print devuelve sus argumentos sin tocarlos, así que puedes intercalarla en medio de una expresión para espiar un valor—. Convierte la línea de comandos en un intérprete interactivo y hace que explorar sea tan barato como preguntar.
Hay además una familia entera de funciones reflexivas que devuelven el estado vivo del editor: la lista de buffers, de ventanas y de pestañas; los mapeos globales y los de un buffer; los autocomandos que coinciden con un evento o un grupo; los comandos de usuario definidos; los grupos de resaltado; los canales abiertos. Ninguna requiere plugin alguno:
vim.print(vim.api.nvim_get_keymap("n")) -- mapeos de modo Normalvim.print(vim.api.nvim_get_autocmds({ event = "BufWritePre" }))vim.print(vim.api.nvim_get_commands({})) -- comandos de usuariovim.print(vim.api.nvim_get_hl(0, { name = "Comment" }))El catálogo de opciones como base de datos
La función central de esta lección, vim.api.nvim_get_all_options_info, devuelve de una sola llamada una tabla con todas las opciones del editor y, por cada una, su nombre corto, su tipo, su valor por defecto, su ámbito, si es global-local, si es una lista separada por comas o un conjunto de banderas, y si ha sido modificada respecto del valor por defecto.
Eso no es un dato curioso: es la respuesta autoritativa a la pregunta que abría la lección 8.2. Ya no hace falta recordar si una opción es de buffer o de ventana, ni fiarse de la intuición.
local info = vim.api.nvim_get_all_options_info()-- El ambito de una opcion concreta, sin abrir la documentacionprint(info.tabstop.scope) -- bufprint(info.wrap.scope) -- winprint(info.ignorecase.scope) -- globalprint(info.undolevels.global_local) -- true-- Todas las opciones de ventana, ordenadaslocal ventana = {}for nombre, meta in pairs(info) do if meta.scope == "win" then ventana[#ventana + 1] = nombre endendtable.sort(ventana)vim.print(ventana)-- Que he cambiado yo respecto del valor por defectolocal tocadas = vim.tbl_filter(function(n) return info[n].was_set end, vim.tbl_keys(info))table.sort(tocadas)vim.print(tocadas)-- Para una sola opcion, con contexto de buffer o ventanavim.print(vim.api.nvim_get_option_info2("formatoptions", { buf = 0 }))La consulta de was_set merece atención especial: es una auditoría de tu configuración obtenida sin leerla. Te dice exactamente qué se ha tocado, incluidas las opciones que cambió un plugin sin que lo supieras. Es el complemento perfecto de :verbose set de la lección anterior, que te dirá además dónde ocurrió cada cambio.
Consulta el ámbito antes de escribir la opción
Antes de añadir una opción a tu configuración, ejecuta :lua= vim.api.nvim_get_option_info2('nombre', {}).scope. Si responde buf o win, ya sabes que su sitio no es el archivo raíz sino after/ftplugin/ o un autocomando. Son tres segundos que ahorran el clásico misterio del segundo archivo.
La metadata de la API y el motor de completado
Un escalón más arriba está la especificación completa de la interfaz. nvim_get_api_info devuelve dos cosas: el identificador del canal actual y la tabla de metadatos con la versión del editor, la lista de tipos, los eventos de interfaz y, sobre todo, el catálogo de funciones con sus parámetros, su tipo de retorno y dos campos que conviene leer bien: since y deprecated_since no son versiones de Neovim sino niveles de API (:help api-level), el contador entero que el contrato usa para razonar sobre compatibilidad.
local _, meta = unpack(vim.api.nvim_get_api_info())vim.print(meta.version) -- major, minor, patch... y api_level-- Que funciones estan obsoletas en esta compilacionlocal muertas = vim.iter(meta.functions) :filter(function(f) return f.deprecated_since ~= nil end) :map(function(f) return f.name .. " -> obsoleta desde el nivel " .. f.deprecated_since end) :totable()vim.print(muertas)Esa consulta es la que conviene ejecutar después de cada actualización mayor: cruza la lista de funciones obsoletas con las que usa tu configuración y tendrás la lista exacta de deuda técnica pendiente, sin esperar a que algo se rompa. Sin prisa, además: el contrato de la API (:help api-contract) promete que lo deprecado no se elimina hasta Neovim 2.0. Los clientes externos obtienen la misma tabla sin abrir el editor con nvim --api-info, y desde Vimscript existe vim.fn.api_info(): tres puertas al mismo manifiesto, porque es literalmente la fuente desde la que se generan el enlace de Lua y el protocolo remoto.
El otro instrumento, menos conocido y sorprendentemente potente, es el motor de completado de la línea de comandos expuesto como función: vim.fn.getcompletion recibe un prefijo y una categoría, y devuelve la lista de candidatos que ofrecería Tab. Las categorías incluyen opciones, funciones, comandos, eventos, grupos de resaltado, tipos de archivo y etiquetas de ayuda:
vim.print(vim.fn.getcompletion("fold", "option")) -- opciones que empiezan por foldvim.print(vim.fn.getcompletion("str", "function")) -- funciones de Vimscriptvim.print(vim.fn.getcompletion("Buf", "event")) -- eventos de autocomandovim.print(vim.fn.getcompletion("lsp", "help")) -- etiquetas de ayudaCon eso se cierra el círculo de la lección anterior: cuando no sabes cómo se llama algo, no hace falta salir a buscar fuera. El editor tiene el índice y te lo entrega como tabla de Lua.
Qué hay bajo el cursor: :Inspect
Queda una pregunta de introspección que no va sobre el editor sino sobre este carácter concreto de este buffer: ¿por qué se pinta así? Desde la 0.9 la respuesta viene de serie. :Inspect muestra, en formato legible, todo lo que actúa en la posición del cursor: las capturas de Treesitter, los grupos de sintaxis clásicos, los tokens semánticos del LSP y las extmarks con resaltado. :Inspect! enseña la versión cruda, y las dos son fachadas de funciones que puedes llamar tú: vim.show_pos y vim.inspect_pos, esta última con filtros y coordenadas explícitas para usarla desde código.
vim.print(vim.inspect_pos()) -- todo lo que actua bajo el cursor, como tablaHoy te sirve para responder «¿qué grupo de resaltado toco para cambiar este color?». En los Niveles 14 a 16 se volverá herramienta de trabajo diaria: es la radiografía de las extmarks, del sistema de resaltado y de los árboles de Treesitter que vas a construir y depurar.
Preguntar sale más barato que recordar
Un :lua= sobre el catálogo de opciones responde en dos segundos preguntas que de memoria se responden mal. La memoria es para los patrones, no para los catálogos.
La introspección audita
La lista de opciones con was_set y la de funciones con deprecated_since convierten tu configuración en algo verificable en lugar de algo que se espera que siga funcionando.
Del descubrimiento a la certeza
Queda una advertencia que impide convertir esta lección en un mal hábito. La introspección revela lo que existe en tu compilación concreta, y eso incluye funciones internas, campos no documentados y detalles de implementación que nadie se ha comprometido a mantener. Descubrir algo por reflexión no autoriza a construir encima.
El método correcto tiene dos tiempos. La introspección descubre: te da el nombre exacto, la forma de la tabla que devuelve, el ámbito real. La documentación certifica: te dice si eso es contrato o casualidad, desde cuándo, y qué lo reemplazará. Usar solo lo primero produce configuraciones que se rompen sin aviso; usar solo lo segundo es más lento de lo necesario. Encadenar ambos es el flujo completo, y es exactamente el que dibuja el diagrama anterior.
Un sistema que se describe a sí mismo cambia lo que significa aprenderlo
Merece la pena detenerse en lo que esta capacidad implica, porque va más allá de una colección de trucos. Un sistema reflexivo es aquel cuyo modelo de sí mismo está disponible para sus propios programas, y Neovim lo es de manera casi total: los metadatos que describen la API no son documentación generada a posteriori sino la misma fuente desde la que se construyen el enlace de Lua, el protocolo remoto y los archivos de ayuda. Cuando consultas el ámbito de una opción en tiempo de ejecución no estás leyendo una copia de la verdad, estás leyendo la verdad. Y eso reordena por completo la estrategia de aprendizaje. En un sistema opaco, aprender es acumular hechos memorizados, y la competencia se mide por cuántos recuerdas; el olvido es pérdida neta y cada versión nueva devalúa parte de lo aprendido. En un sistema reflexivo, aprender es adquirir métodos de interrogación, y la competencia se mide por la velocidad con que formulas la pregunta correcta; el olvido es irrelevante porque el catálogo está siempre disponible, y una versión nueva no devalúa nada porque tus preguntas siguen siendo válidas y devuelven las respuestas actualizadas. Por eso este nivel termina aquí y no en una lista de funciones útiles. Lo que se lleva quien lo ha entendido no es un repertorio sino un procedimiento: ante cualquier duda, preguntar al editor por el nombre, preguntar al catálogo por la forma, preguntar a la ayuda por la garantía, y solo entonces escribir la línea. Tres preguntas de diez segundos que sustituyen a media hora de prueba y error, y que además producen una configuración que sabes defender línea por línea. Esa es, finalmente, la única definición operativa de dominar Neovim que resiste el paso del tiempo.
La tabla trae scope, type, default, flaglist y was_set. Compárala con lo que habrías respondido de memoria.
Son varios cientos. Nadie las memoriza: se filtran. Repite con un filtro nvim_win_ para quedarte con las de ventana.
El motor de completado como función: el mismo índice de Tab, pero como tabla que puedes filtrar y cruzar con el catálogo de opciones.
Verás las capturas de Treesitter, los tokens semánticos del LSP y las extmarks activas en esa posición, con sus grupos de resaltado. La versión cruda, para código, es vim.inspect_pos().
Construye tu propio explorador
- Averigua con una sola línea el ámbito y el tipo de cinco opciones que uses a diario. Compara el resultado con lo que habrías respondido de memoria.
- Obtén la lista de opciones con
was_seten tu sesión y contrástala con tu configuración. Investiga con:verbose setlas que no reconozcas. - Cruza las funciones con
deprecated_sincede tu compilación con las que aparecen en tus archivos. Corrige al menos una —si usasnvim_exec, ya tienes candidata de la lección 8.1. - Escribe una función que reciba un texto, use
getcompletionpara buscar opciones que lo contengan y muestre cada coincidencia con su ámbito y su valor actual. Son diez líneas y es tu primer instrumento de introspección propio. - Abre un archivo de código con LSP activo, ejecuta
:Inspectsobre un token coloreado y localiza cuál de las capas —sintaxis, Treesitter, tokens semánticos, extmarks— está ganando. Cambia el color de ese grupo connvim_set_hly verifica el efecto. - Toma un campo no documentado que hayas encontrado por reflexión y busca su etiqueta en la ayuda. Si no existe, decide conscientemente no construir nada encima y anota por qué.