¿Cómo convertir tu configuración de init.vim a init.lua?

Requisitos

  1. Curiosidad. Es la condición más importante para leer este post. Si la tendrás, con algo de esfuerzo sumado a algo de tiempo podrás aprender Vim, Neovim, etc.
  2. Conocimientos básicos o intermedios de Vim y haber leído el post anterior.
  3. Conocimientos básicos de vim-plug.

Repaso de conceptos importantes

En el post anterior vimos como un usuario de Vim puede comenzar a Neovim. En el presente artículo haremos la transición de Vim script a Lua. Ok, booting the post 😄 ...

De Vim script a Lua

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 alternativo, lo que ha ganado popularidad debido a su mejor rendimiento y facilidad de uso comparado con Vim script tradicional.

Cabe remarcar que Neovim usa una implementación de Lua llamada LuaJIT. LuaJIT es capaz de compilar código Lua a código máquina nativo durante la ejecución, lo que resulta en un rendimiento significativamente superior al intérprete estándar de Lua. LuaJIT es ampliamente reconocido por su alta velocidad, siendo en muchos casos comparable al rendimiento de lenguajes compilados como C. En el contexto de Neovim, LuaJIT permite que los scripts escritos en Lua se ejecuten con un rendimiento muy superior la de Lua estándar.

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.

Entonces:

  • Neovim usa LuaJIT para ejecutar configuraciones en Lua de forma rápida.
  • Casi todas las instalaciones modernas de Neovim usan LuaJIT por defecto.
  • Esto significa que los scripts y configuraciones Lua en Neovim corren a velocidades cercanas al código nativo.

Ventaja clave: Permite que Neovim con Lua sea mucho más rápido que Vim script o incluso Vim9script en muchos casos.

3 conceptos esenciales — lo mínimo que siempre debés recordar

Concepto Por qué es esencial
Tablas (table) Todo en Lua usa tablas. 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.tbl_keys(...)
  • Configuraciones de plugins
  • Listas/patrones

No son tablas ⚠ :

  • Funciones
  • Comandos ejecutados
  • Strings, números, booleanos

El podio de las tablas madres más importantes en Neovim

Estas son las tres tablas raíz (madre) 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 vim como una de sus claves.

🥉 package (de Lua)

Maneja los módulos cargados por require. Es crucial para modularizar tu configuración:

  • package.loaded permite ver y recargar módulos.

Nota

Tablas como vim.api, vim.opt, vim.g son importantes, pero no son madres: 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 Opciones globales que contienen listas set nf+=alpha
vim.cmd Comandos que se ejecutan en modo ex colorscheme PaperColor
vim.g Variables Globales let g:loaded_perl_provider =
vim.keymap.set Asignación de atajos de teclado a una función o combinación de teclas vim.keymap.set('n', 'ñ', ':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 (no hay una manera clara) 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() ... end function hola() ... end
Función anónima no hay function() ... end
Autocomando autocmd BufWrite ... vim.api.nvim_create_autocmd
Mapping nnoremap vim.keymap.set
Comando command ... vim.api.nvim_create_user_command

Comparativa de conceptos clave: Vim script vs Lua

Concepto Vim script Lua (Neovim)
Variables y configuración Variables globales (let g:var), set para opciones Tablas (vim.g, vim.o, vim.opt) y variables locales (local)
Automatización / lógica Funciones (function!), comandos (command), autocmds Funciones anónimas y definidas, vim.api.*, autocmds con vim.api.nvim_create_autocmd
Extensibilidad y estructura Scripts planos, difícil modularizar Modularización con require, package.loaded, uso de archivos Lua por secciones

Ejemplo de migración Vim script → Lua

Bueno, con la teoría suficiente, ✋✋ a la obra 😉. Supongamos que tenemos esta configuración de archivo init.vim:

set tabstop=4
set nf+=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

El sistema modular de 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 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  
mv ~/.vim/autoload/plug.vim  ~/.local/share/nvim/site/autoload
  • El directorio ~/.config/nvim es obligatorio.
  • El directorio ~/.config/nvim/lua no es obligatorio, pero es muy útil para modularizar la configuración de Neovim.
  • Los nombres dados de los archivos y directorios dentroo de ~/.config/nvim/lua son 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 nuestros módulos

local commands = require('utils.commands')
local extracommands = require('utils.extracommands')
local keymaps = require('keymaps')
local opciones = require('opciones')


-- Mantenemos por ahora la configuración de plugins en Vim script
vim.cmd('source ~/.config/nvim/plugins.vim')

Archivo ~/.config/nvim/keymaps.lua

-- Alias
local map = vim.keymap.set                                                                         
-- maps
map('n', 'ñ', ':wq<CR>

Archivo ~/.config/nvim/lua/opciones.lua

-- Alias
local vo = vim.o
local vop = vim.opt

-- Opciones
vo.tabstop = 4
vop.nf:append('alpha')

Archivo ~/.config/nvim/lua/utils/commands.lua

-- Crea una tabla vacía para guardar funciones

local M = {}

function M.create_command(name, command_or_function, opts)
  pcall(vim.api.nvim_del_user_command, name)
  vim.api.nvim_create_user_command(name, command_or_function, opts or {})
end

-- Alias
local com = M.create_command

com('Silent', function(opts)
  vim.cmd('silent !' .. table.concat(opts.fargs, ' '))
  vim.cmd('redraw!')
end, { nargs = '+' })

com('Nbuild', function()
  vim.cmd('w')
  vim.cmd('silent nikola build')
end, {})


return M

Archivo ~/.config/nvim/lua/utils/extracommands.lua

-- Alias
local vcmd = vim.cmd

-- Extra commands
vcmd('filetype plugin on')
vcmd('runtime plugins.vim')
vcmd('colorscheme PaperColor')

Archivo ~/.config/nvim/plugins.vim

Este es el único archivo que lo dejamos en Vim script (con código Lua embebido incluido):

```vim
"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 3 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 etipear tanto 😉.

¿Para qué agregamos la función create_command?

En Lua cuando usás la API de Neovim, si querés redefinir una función de un comando personalizado, tenés que borrarlo y volver definirlo, sino da error. Esto es para evitar sobreescrituras accidentales. Por eso creamos la función create_command para que cuando redefinamos un comando lo hagamos de manera explícita.

  • Usa pcall para borrar el comando personalizado y si no existe no da error.
  • Vuelve a recrearlo con la nueva definición.

¿Acaso el comando Nbuild hace lo mismo en Vim script que en Lua? Vamos a analizarlo.

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 com('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.
local commands = require('utils.commands')
commands.create_command(...)

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 el viejo .vimrc) a init.lua.

Migrar tu configuración de Neovim de Vim script a Lua no solo representa una mejora técnica inmediata, sino también una oportunidad para afilar tu pensamiento en términos de estructura, modularidad y optimización.

Lua te invita a organizar tu configuración como un sistema flexible y componible, donde cada parte puede ser reutilizada, testeada y afinada con precisión. Este tipo de enfoque —más declarativo, más explícito— entrena tu mente en formas modernas de entender y construir software.

A medida que domines la integración de herramientas, el manejo eficiente de datos y la automatización de tareas dentro de Neovim, estarás cultivando hábitos que trascienden tu editor: aprenderás a diseñar entornos complejos donde múltiples componentes cooperan de forma orquestada.

Esa forma de pensar, de buscar la mejora continua y construir sistemas cada vez más elegantes y potentes, es lo que hoy distingue a los profesionales más relevantes del ecosistema tecnológico.

Apéndice : Los 10 conceptos fundamentales de Lua (resumen)

Si querés profundizar, te propongo 7 conceptos para profundizar en el uso de Lua en Neovim:

Concepto Descripción breve
Tablas (table) Estructuras clave-valor o listas. Base de toda la configuración.
Variables locales (local) 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.*) Puente entre Lua y Neovim: crear comandos, mappings, autocmds.
vim.opt vs vim.o Para configurar opciones. vim.o para valores simples, vim.opt para manipular 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

Comentarios

Comments powered by Disqus