¿Cómo convertir tu configuración de init.vim a init.lua?
Requisitos
- Curiosidad. Es la condición más importante para leer este post. Si la tenés, con algo de esfuerzo sumado a algo de tiempo podrás aprender Vim, Neovim, etc.
- Conocimientos básicos o intermedios de Vim y haber leído el post anterior.
- Conocimientos básicos de vim-plug.
Repaso de conceptos importantes
En el post anterior vimos cómo un usuario de Vim puede comenzar a usar Neovim. En el presente artículo haremos la transición de Vim script a Lua. Ok, booting the post 😄 ...

Recordemos que Vim usa Vim script (aka VimL) como lenguaje de scripting nativo. Es un lenguaje específico que permite personalizar y extender su funcionalidad. Existe en dos formas: el Vim script tradicional (también llamado "legacy script") y Vim9script (introducido en Vim 9.0), que ofrece mejor rendimiento y una sintaxis más moderna. Sin embargo, Vim9script ni siquiera se usa de manera predeterminada en Vim 9.
Lua, en cambio, es un lenguaje de programación ligero, de alto nivel y multiparadigma, diseñado principalmente para ser incorporado en aplicaciones. Se destaca por su simplicidad, eficiencia y portabilidad. Neovim incorporó Lua como lenguaje de scripting de primera clase, lo que ha ganado popularidad debido a su mejor rendimiento y facilidad de uso comparado con Vim script tradicional.
Neovim usa una implementación de Lua llamada LuaJIT, que compila código Lua a código máquina nativo durante la ejecución. Es un detalle técnico relevante, pero no la razón principal para preferir Lua sobre Vim script en la configuración del editor — en ese terreno el cuello de botella suele estar en el startup time y en el diseño de los plugins, no en la velocidad del lenguaje.
Diferencia de idiosincrasia:
- Vim script: scripting lineal, global y directo. Bueno para pequeños ajustes, pero difícil de escalar.
- Lua (Neovim): lenguaje estructurado, orientado a modularizar, reusar y escalar. Favorece el uso de variables locales, tablas y funciones que mejoran el mantenimiento de la configuración.
Por qué importa esto en la práctica:
- La API de Neovim en Lua es estructurada y predecible (tablas, namespaces como
vim.api.*,vim.opt), donde Vim script ofrecía variables globales y comandos planos. - El ecosistema de plugins modernos asume Lua como lenguaje nativo — los gestores actuales (
lazy.nvim,packer.nvim), los runtime helpers, los plugins de LSP y UI conviven mejor con configs en Lua. - La modularización con
requirepermite dividir la configuración en archivos lógicos con scoping limpio, algo que Vim script soporta de forma rudimentaria.
LuaJIT existe y es rápido, pero esa rapidez rara vez es lo que el sysadmin nota al usar Neovim — lo que nota es que su configuración escala mejor y se integra con el ecosistema sin fricciones.
3 conceptos esenciales — lo mínimo que siempre debés recordar
| Concepto | Por qué es esencial |
|---|---|
Tablas (table) |
Las tablas son la única estructura de datos compuesta en Lua. Opciones (vim.o, vim.opt), autocmds, comandos, mappings y configuraciones de plugins. |
Variables locales (local) |
Permiten organizar el código, evitar errores y mejorar el rendimiento. Imprescindible para no contaminar el entorno global. |
API de Neovim (vim.api.*) |
Es la manera oficial y flexible de interactuar con Neovim: crear comandos, autocmds, mappings y manipular el editor desde Lua. |
Si entendés estos 3 conceptos, podés crear y mantener cualquier configuración básica y muchas avanzadas.
¿Qué es una tabla y qué no en la configuración de Neovim (Lua)?
Tablas 🆗:
- vim.g
- vim.opt / vim.o
- vim.api
- Configuraciones de plugins
- Listas/patrones
No son tablas ⚠:
- Funciones
- Comandos ejecutados
- Strings, números, booleanos
El podio de las tablas globales más importantes en Neovim
Estas son las tres tablas globales más importantes del entorno Lua en Neovim:
🏅 vim
Es la tabla principal y núcleo del entorno Lua en Neovim. Contiene todo lo necesario para interactuar con el editor:
- Subtablas como
vim.api,vim.cmd,vim.opt,vim.g,vim.fn, etc. - Siempre disponible sin necesidad de importar.
🥈 _G
Es la tabla global del lenguaje Lua. Desde aquí podés definir funciones o variables globales accesibles desde cualquier parte de tu configuración:
_G.mi_funcion = function() ... end- También contiene
vimcomo una de sus claves.
🥉 package (de Lua)
Maneja los módulos cargados por require. Es crucial para modularizar tu configuración:
-
package.loadedpermite ver y recargar módulos.
Nota
Tablas como vim.api, vim.opt, vim.g son importantes, pero no son globales de nivel superior: viven dentro de vim. Lo mismo aplica para math, string, table, etc., que viven en _G.
→ Estas 3 (vim, _G, package) forman la base del entorno ejecutable de cualquier configuración Neovim moderna en Lua.
Subtablas más importantes
| Tabla | Contiene | Ejemplo equivalente en Vim script |
|---|---|---|
vim.o |
Opciones globales | set tabstop=4 |
vim.opt |
Acceso a opciones con métodos (:append, :remove, :prepend); requerido para listas |
set nrformats+=alpha |
vim.cmd |
Comandos que se ejecutan en modo ex | colorscheme PaperColor |
vim.g |
Variables globales | let g:loaded_perl_provider = 0 |
vim.keymap.set |
Asignación de atajos de teclado a una función o combinación de teclas | nnoremap ñ :wq<CR> |
vim.api.nvim_create_user_command |
Creación de comandos personalizados | command Nbuild execute 'w | Silent nikola build' |
Diccionario cruzado Vim script / Lua
| Concepto | Vim script | Lua |
|---|---|---|
| Variable global | let g:mi_var = 10 |
vim.g.mi_var = 10 |
| Variable local |
let l:var = 10 (function-local), s:, b:, w: por contexto |
local var = 10 |
| Lista | let lista = [1,2,3] |
lista = {1, 2, 3} |
| Diccionario | let dict = {'a':1} |
dict = {a = 1} |
| Función | function! Hola() ... endfunction |
function hola() ... end |
| Función anónima |
{a, b -> a + b} (lambda, desde Vim 8.0) |
function() ... end |
| Autocomando | autocmd BufWrite ... |
vim.api.nvim_create_autocmd |
| Mapping | nnoremap |
vim.keymap.set |
| Comando | command ... |
vim.api.nvim_create_user_command |
Ejemplo de migración Vim script → Lua
Bueno, con la teoría suficiente, ✋✋ a la obra 😉. Supongamos que tenemos esta configuración del archivo init.vim:
set tabstop=4 set nrformats+=alpha nnoremap ñ :wq<CR> command -nargs=+ Silent execute 'silent !<args>' | redraw! command Nbuild execute 'w | Silent nikola build' filetype plugin on runtime plugins.vim colorscheme PaperColor
Es un archivo pequeño, pero que sirve de punto de partida para moverse a una configuración en Lua.
Crear el entorno para Neovim

Como vimos en el post anterior, mi recomendación es comenzar con un archivo init.vim. Una vez que ya estamos usando Neovim podemos realizar la migración a init.lua. Sin embargo, tenemos que crear los directorios para que Neovim guarde su configuración. Lo único que dejaremos por ahora en Vim script es la configuración de plugins con vim-plug. Por lo tanto ejecutamos:
mkdir -p ~/.config/nvim/lua/utils mkdir -p ~/.local/share/nvim/site/autoload # Suponiendo que usabas un archivo para gestionar plugins en Vim mv ~/.vim/autoload/plug.vim ~/.local/share/nvim/site/autoload
- El directorio
~/.config/nvimes obligatorio. - El directorio
~/.config/nvim/luano es obligatorio, pero es muy útil para modularizar la configuración de Neovim. - Los nombres dados de los archivos y directorios dentro de
~/.config/nvim/luason totalmente arbitrarios.
En Neovim tendremos una estructura más modular. Si bien podemos tener todo en init.lua, vamos a aprovecharnos de esta configuración que es mucho más flexible.
Archivo ~/.config/nvim/init.lua
-- Cargamos los plugins primero — algunas configuraciones -- (como `colorscheme`) dependen de tenerlos disponibles en el runtimepath vim.cmd('source ~/.config/nvim/plugins.vim') -- Cargamos nuestros módulos (el require ejecuta el archivo; -- el efecto viene del side-effect, no del valor capturado) require('utils.commands') require('utils.extracommands') require('keymaps') require('opciones')
Archivo ~/.config/nvim/lua/keymaps.lua
-- Alias local map = vim.keymap.set -- maps map('n', 'ñ', ':wq<CR>')
Nota sobre
vim.keymap.set: usaremap = falsepor default, así quemap('n', 'ñ', ':wq<CR>')ya es equivalente annoremap(no anmap). El "nore" que en VimL diferenciabannoremapdenmapestá implícito en el default de la API Lua.
Archivo ~/.config/nvim/lua/opciones.lua
-- Alias local vo = vim.o local vop = vim.opt -- Opciones vo.tabstop = 4 vop.nrformats:append('alpha')
Archivo ~/.config/nvim/lua/utils/commands.lua
-- Comando auxiliar para correr cosas en shell silenciado vim.api.nvim_create_user_command('Silent', function(opts) vim.cmd('silent !' .. table.concat(opts.fargs, ' ')) vim.cmd('redraw!') end, { nargs = '+' }) -- Build del sitio Nikola vim.api.nvim_create_user_command('Nbuild', function() vim.cmd('w') vim.cmd('Silent nikola build') end, {})
Archivo ~/.config/nvim/lua/utils/extracommands.lua
-- Alias local vcmd = vim.cmd -- Extra commands vcmd('filetype plugin on') -- Esta configuración es innecesaria porque ya está en init.lua -- vcmd('runtime plugins.vim') vcmd('colorscheme PaperColor')
Archivo ~/.config/nvim/plugins.vim
Este es el único archivo que dejamos en Vim script (con código Lua embebido):
"Plugins (Plug) call plug#begin('~/.vim/plugged') Plug 'itchyny/lightline.vim' Plug 'nyngwang/NeoTerm.lua' Plug 'NLKNguyen/papercolor-theme' call plug#end() lua << EOF require("neo-term").setup { exclude_filetypes = { "oil" } } EOF nnoremap <F4> :NeoTermToggle<CR> tnoremap <F4> <C-\><C-n>:NeoTermToggle<CR> tnoremap <Esc> <C-\><C-n>:NeoTermEnterNormal<CR>
Y listo...
Explicación
¿Por qué definimos alias en los archivos de módulos?
Los alias son básicamente variables. Los definimos porque este es un ejemplo de tipo "esqueleto" de configuración. Cuando comiences a agregar más opciones, atajos de teclado y comandos personalizados, no querrás tipear tanto 😉.
Nota sobre redefinición de comandos
vim.api.nvim_create_user_command sobrescribe por default un comando con el mismo nombre (la opción force tiene valor true por defecto, según la doc oficial de la API). Es decir: podés llamar dos veces a nvim_create_user_command('Nbuild', ...) y la segunda definición reemplaza a la primera sin error. No hace falta borrar antes con nvim_del_user_command. Esto es distinto del :command de VimL, que sí requiere command! para sobrescribir — la API Lua no hereda esa restricción.
El ejemplo de Nbuild deja ver diferencias más grandes entre VimL y Lua. Las analizamos en cinco dimensiones:
1. Brevedad vs mantenibilidad
- VimL:
vim
command -nargs=+ Silent execute 'silent !<args>' | redraw!
command Nbuild execute 'w | Silent nikola build'
Es breve, directo y funciona. Pero:
- Es poco expresivo
- No permite manejar errores fácilmente
-
No escala bien con lógica compleja
-
Lua:
lua
vim.api.nvim_create_user_command('Silent', function(opts)
vim.cmd('silent !' .. table.concat(opts.fargs, ' '))
vim.cmd('redraw!')
end, { nargs = '+' })
Es más verboso, sí, pero:
- La función puede ser más compleja si se necesita
- Es programable y reutilizable
- Puede integrarse con condicionales, bucles, o lógica externa
2. Control de errores y depuración
- En VimL: si un comando falla, tenés poco margen para capturar y manejar el error.
- En Lua: podés usar
pcall,vim.notify,vim.fn.systemlist, y construir mensajes personalizados o fallback si algo sale mal.
local ok, _ = pcall(function() vim.cmd('silent !nikola build') end) if not ok then vim.notify('Error al ejecutar nikola build', vim.log.levels.ERROR) end
3. Modularización
- En Lua podés agrupar comandos en módulos, con
require(...). - En VimL tenés que recurrir a
runtime,source, y no hay namespaces claros.
require('utils.commands') -- registra los user commands al cargarse require('utils.extracommands') -- aplica colorscheme y filetype detection require('keymaps') -- registra los atajos de teclado require('opciones') -- opciones generales de configuración
Esto facilita dividir la configuración en archivos lógicos, más fácil de leer, mantener y compartir.
4. Compatibilidad futura y ecosistema Neovim
- Neovim ha declarado que Lua es su lenguaje de configuración nativo moderno.
- La mayoría de los plugins modernos están escritos en Lua.
- VimL sigue siendo soportado, pero está siendo superado funcionalmente.
5. Legibilidad a largo plazo
-
Aunque VimL sea más corto, en un entorno de colaboración o con configuraciones grandes, Lua:
-
Es más explícito.
- Permite documentar.
- La API de Neovim en Lua permite un uso más explícito y predecible de tipos de datos.
- Es más fácil de extender con lógica externa.
Conclusión
Si sabés migrar opciones, mappings, comandos y cargar plugins → podés convertir el 80% o más de tu init.vim (equivalente al viejo .vimrc) a init.lua.
Migrar de Vim script a Lua no es una mejora cosmética: cambia las posibilidades de la configuración. Lo que en Vim script son comandos breves, en Lua son funciones programables — más verbosas, sí, pero abiertas a control de errores con pcall, modularización con require y un ecosistema de plugins que ya asume Lua como lenguaje nativo.
La estructura mostrada acá es una entre muchas. El esqueleto init.lua + módulos en lua/ se sostiene porque escala: cada archivo crece sin contaminar al resto, y la configuración deja de ser una pared de líneas para volverse un conjunto de piezas que podés rearmar cuando lo necesites.
Lo que no migramos en este post es la configuración de plugins, que dejamos en plugins.vim. Reemplazar vim-plug por un gestor en Lua como lazy.nvim es el siguiente paso natural — y tema de este otro post.
Al cierre del post dejé un resumen de 10 conceptos clave para tener a mano cuando empieces a extender tu config.
Apéndice: Los 10 conceptos fundamentales de Lua (resumen)
Resumen de los 10 conceptos clave de Lua en Neovim. Los 3 marcados como (esencial) son los que vimos al principio; el resto extiende esa base:
| Concepto | Descripción breve |
|---|---|
Tablas (table) (esencial)
|
Estructuras clave-valor o listas. Base de toda la configuración. |
Variables locales (local) (esencial)
|
Para mantener el código limpio y evitar contaminación global. |
| Funciones anónimas | Permiten definir acciones sin nombre, usadas en mappings, autocmds y comandos. |
API de Neovim (vim.api.*) (esencial)
|
Puente entre Lua y Neovim: crear comandos, mappings, autocmds. |
vim.opt vs vim.o
|
vim.o da acceso directo al valor; vim.opt lo envuelve en un objeto type-aware con métodos (necesario para listas). |
require |
Para cargar módulos externos y dividir la configuración. |
| Paréntesis en llamadas | Obligatorio usar () al llamar funciones. |
| Referencias | Asignar una tabla a otra variable sin copiarla (ejemplo: local vg = vim.g). |
_G (global) |
Tabla especial que contiene las variables y funciones globales accesibles en toda la configuración. |
package.loaded |
Permite ver o manipular qué módulos están cargados, útil para recargar configuraciones dinámicamente. |
Fuentes y más Recursos
-
Lua en Neovim (
:help lua) — referencia completa de la API Lua de Neovim. -
Guía de Lua para Neovim (
:help lua-guide) — survival kit oficial para configurar Neovim en Lua. - Manual de referencia de Lua 5.1 — definición oficial del lenguaje (LuaJIT, que usa Neovim, está basado en esta versión).
- Recursos de Lua recomendados por Neovim — curaduría oficial de tutoriales y libros.
- A pragmatic approach to migrating from VSCode to Neovim — otra mirada sobre migración hacia Neovim+Lua, desde VSCode.
- NeoVim Is Better, But Why Developers Aren't Switching To It? — perspectiva crítica sobre la adopción de Neovim.
Comentarios
Comments powered by Disqus