Desarrollo local: apuntar, recargar y un bucle de iteración corto
Trabajar sobre el plugin desde su directorio real en lugar de la copia del gestor, recargar módulos sin reiniciar entendiendo qué invalida package.loaded y qué no, aislar fallos con una configuración mínima reproducible y cerrar el bucle con pruebas sin interfaz.
El obstáculo real al escribir tu primer plugin no es la API sino la latencia del ciclo de realimentación. Editar un fichero, salir del editor, volver a entrar, reproducir el estado que provocaba el problema y comprobar si el cambio sirvió cuesta entre quince y sesenta segundos, y a ese precio uno deja de experimentar y empieza a razonar en el vacío, que es exactamente el modo en que se escriben plugins frágiles. Reducir ese ciclo a dos segundos no es una comodidad: cambia qué preguntas te permites hacerle al sistema. Este capítulo trata de las tres piezas que lo consiguen —trabajar sobre el directorio real, invalidar la caché de módulos con precisión y aislar cada fallo en la configuración más pequeña que lo reproduzca— y, sobre todo, de los límites exactos de la recarga en caliente, porque creer que un módulo se recargó cuando no lo hizo produce las horas de depuración más inútiles de la profesión.
- Dirigir el gestor de plugins al directorio de trabajo sin duplicar copias ni tocar el repositorio instalado.
- Invalidar
package.loadedcon precisión y explicar qué recupera una recarga y qué no. - Reproducir cualquier fallo en una configuración mínima que descarte la interferencia de otros plugins.
- Cerrar el ciclo con pruebas sin interfaz gráfica ejecutables desde la terminal en menos de un segundo.
Apuntar el gestor a tu directorio real
La opción ingenua es clonar el plugin donde el gestor guarda sus descargas y editar ahí. Funciona hasta la primera sincronización, que descarta tus cambios sin avisar. La opción correcta es declarar la ruta local como origen; todos los gestores modernos lo admiten y el mecanismo subyacente es siempre el mismo, insertar tu carpeta en el runtimepath y no tocarla nunca.
-- Con lazy.nvim: una spec que apunta a disco. El campo dev evita cualquier red.
return {
"willy/pico.nvim",
dir = vim.fn.expand("~/dev/pico.nvim"),
dev = true,
cmd = "Pico",
opts = { autoguardar = true },
}
-- Alternativa global: todo lo que este bajo ese directorio se toma en local
require("lazy").setup({
spec = { { import = "plugins" } },
dev = { path = "~/dev", patterns = { "pico" }, fallback = true },
})
# Comprobar que el editor esta leyendo TU carpeta y no una copia descargada
nvim --headless -c 'lua vim.print(vim.api.nvim_get_runtime_file("lua/pico/init.lua", true))' -c q
Sin gestor de por medio, el equivalente cabe en una línea de prepend sobre la lista de rutas, y merece la pena conocerlo porque es lo que usarás en las configuraciones de prueba. Un detalle que ahorra confusión: si el gestor ya tenía instalada una versión publicada del mismo plugin, retírala antes. Dos copias en el runtimepath no dan error, dan comportamiento decidido por el orden, y depurar el fichero equivocado es un rito de iniciación que conviene saltarse.
Recargar sin reiniciar
require devuelve lo memorizado en package.loaded y no vuelve a leer el disco. Recargar consiste, por tanto, en borrar esas entradas y volver a pedirlas. La operación es trivial de escribir y la trampa está en el alcance: hay que invalidar el árbol entero de tu plugin, no solo el módulo que editaste, porque los submódulos memorizados seguirían siendo los viejos.
-- lua/pico/dev.lua : recarga completa del arbol de modulos del plugin
local function recargar(raiz)
for nombre, _ in pairs(package.loaded) do
if nombre == raiz or nombre:sub(1, #raiz + 1) == raiz .. "." then
package.loaded[nombre] = nil
end
end
return require(raiz)
end
vim.api.nvim_create_user_command("PicoRecarga", function()
pcall(vim.api.nvim_del_augroup_by_name, "Pico") -- retira efectos anteriores
recargar("pico").setup(vim.g.pico_opts or {}) -- vuelve a construirlos
vim.notify("pico recargado", vim.log.levels.INFO)
end, { desc = "recarga el plugin en caliente" })
Fíjate en que la recarga no es solo require: hay que deshacer antes lo que la carga anterior hizo fuera del módulo. Un grupo de autocomandos recreado con vaciado, los atajos retirados o sobrescritos, los comandos de usuario redefinidos, los temporizadores parados. Si tu plugin ya cumple la idempotencia del capítulo anterior, la recarga es casi gratis; si no, cada recarga acumula un efecto duplicado y acabarás persiguiendo un fallo que solo existe en tu sesión de desarrollo.
Invalidar la caché sustituye las tablas de los módulos, no las referencias que otros ya capturaron. Una función registrada como callback de un autocomando, guardada en un atajo, pasada a un temporizador o cerrada dentro de otra función sigue apuntando a la versión antigua para siempre. Lo mismo ocurre con los valores en local de nivel superior de otro módulo que ya te importó, y con cualquier estado externo al intérprete: procesos hijos, sockets, buffers creados. Regla operativa: si el cambio afecta a la lógica interna de una función, la recarga sirve; si afecta a cómo o cuándo se registran los efectos, reinicia y no discutas contigo mismo.
Aislar: la configuración mínima reproducible
Cuando algo falla, la primera pregunta es si falla tu plugin o la interacción de tu plugin con los otros cuarenta que tienes instalados. Responderla cuesta diez segundos con un archivo de arranque desechable que cargue el editor limpio y solo tu código. Es además el artefacto exacto que debes adjuntar a un informe de fallo, tuyo o ajeno.
-- repro.lua : configuracion minima, sin tu configuracion personal
vim.env.LAZY_STDPATH = ".repro"
vim.opt.rtp:prepend(vim.fn.expand("~/dev/pico.nvim"))
require("pico").setup({ autoguardar = false })
vim.keymap.set("n", "<leader>r", "<cmd>PicoRecarga<cr>", { desc = "recargar" })
# Arranque limpio: ignora tu init personal y usa solo el fichero de reproduccion
nvim --clean -u repro.lua nota.md
# Comprobar el coste de arranque y ordenar por tiempo descendente
nvim --startuptime /tmp/tiempos.log +q && sort -k2 -nr /tmp/tiempos.log | head -20
La disposición que mejor funciona no requiere ninguna herramienta especial: un panel del multiplexor con el editor arrancado en modo aislado sobre un fichero de prueba fijo, otro con el código del plugin, y el mismo atajo de recarga en ambos. Añade al fichero de reproducción un autocomando que recargue al guardar cualquier fichero bajo el directorio del plugin y habrás eliminado incluso el paso de pulsar la tecla, a cambio de perder el control sobre cuándo ocurre la recarga, que en ciertas depuraciones importa. Empieza por el atajo explícito.
El bucle completo queda entonces así: una ventana del multiplexor con el editor abierto en modo aislado, otra con el código del plugin, y un atajo que recarga y vuelve a ejecutar el caso de prueba. Dos segundos por iteración. A esa velocidad se puede trabajar por experimentos —cambiar una cosa, mirar qué pasa— en lugar de por deducción, y para código que interactúa con un editor interactivo el experimento es casi siempre más rápido que la deducción.
flowchart LR a[Editar el modulo en disco] --> b[Atajo de recarga] b --> c[Invalidar el arbol en package loaded] c --> d[Deshacer efectos del grupo anterior] d --> e[Volver a llamar a setup] e --> f[Ejecutar el caso de prueba] f --> a f --> g[Si el cambio toca el registro de efectos reiniciar limpio] style b fill:#a6e3a1,color:#11111b style g fill:#f9e2af,color:#11111b
Cerrar el ciclo: pruebas sin interfaz
La recarga en caliente acelera la exploración; las pruebas automáticas evitan que lo explorado se rompa mañana. Neovim se ejecuta sin interfaz y sigue siendo un editor completo, con buffers, ventanas y toda la API disponible, de modo que una prueba puede abrir un buffer, ejecutar tu comando y comprobar el contenido resultante en unas decenas de milisegundos.
# Una prueba completa, sin dependencias, desde la terminal
nvim --headless --clean -u repro.lua \
-c "lua assert(require('pico').get().autoguardar == false)" \
-c "lua vim.cmd.Pico() ; assert(vim.bo.filetype == 'markdown')" \
-c "qa!"
# Con marco de pruebas: recorre el directorio de specs y devuelve codigo de salida
nvim --headless -c "PlenaryBustedDirectory tests/ { minimal_init = 'repro.lua' }"
Tres hábitos convierten esto en una red de seguridad real. El primero es probar la frontera antes que la lógica: que setup sin argumentos funcione, que una opción inválida produzca error, que dos llamadas no dupliquen efectos. El segundo es escribir una prueba cada vez que corriges un fallo, porque el fallo que apareció una vez volverá tras la próxima refactorización. El tercero es integrarlo en el repositorio desde el principio: un comando de una línea que cualquiera pueda ejecutar antes de enviarte un parche vale más que cualquier documento de contribución.
Hay una idea aquí que trasciende con mucho la escritura de plugins, y es la única de este nivel que aplicarás en todos los sistemas que construyas durante el resto de tu carrera. El tiempo que transcurre entre hacer un cambio y observar su efecto no es un parámetro de comodidad del entorno: determina la clase de razonamiento que eres capaz de aplicar al problema. Con un ciclo de dos segundos formulas una hipótesis, la pruebas y descartas la mitad del espacio de búsqueda; puedes permitirte experimentos baratos, preguntas tontas que resultan reveladoras, exploraciones sin objetivo definido. Con un ciclo de un minuto el coste de cada experimento supera al de pensar, así que dejas de experimentar y empiezas a construir modelos mentales del sistema, que es una actividad noble y también la fuente principal de errores, porque tu modelo mental de un editor con cuarenta plugins es inevitablemente falso en algún punto que no sospechas. Con un ciclo de diez minutos, sencillamente dejas de intentar cosas. La consecuencia es que optimizar el bucle no compite con el trabajo real: es el trabajo real, y una hora invertida en un atajo de recarga y un archivo de reproducción se amortiza el mismo día. Observa además que la recarga en caliente solo funciona limpiamente si el plugin ya está bien diseñado: si los efectos son idempotentes, si el estado vive en un sitio identificable, si deshacer lo hecho es posible. Es decir, la capacidad de iterar rápido no es algo que se añada a un sistema al final, sino una propiedad que emerge de haberlo estructurado con fronteras nítidas entre configuración, efectos y estado. Esa es la razón profunda por la que los sistemas fáciles de probar tienden a ser también los sistemas bien diseñados, y por la que la dificultad para escribir una prueba rápida es el indicador más fiable de que hay un problema de arquitectura esperándote más adelante.
- Apunta tu gestor al directorio local de tu plugin y confirma con
nvim_list_runtime_pathsque no queda ninguna copia instalada compitiendo. - Implementa el comando de recarga completo, incluyendo el borrado del grupo de autocomandos, y ligalo a un atajo.
- Demuestra el límite: guarda una función del plugin dentro de un atajo, recarga, modifica esa función y comprueba que el atajo sigue ejecutando la versión antigua.
- Escribe tu archivo de reproducción mínima y arranca con
--cleanpara verificar que el plugin funciona sin tu configuración personal. - Convierte tres comprobaciones manuales en un comando sin interfaz que devuelva código de salida distinto de cero al fallar, y mide cuánto tarda.