Lua en Neovim: configurar y extender el editor con Lua en lugar de Vimscript

Durante décadas, personalizar Vim significaba escribir Vimscript: un lenguaje peculiar, con una sintaxis que mezcla comandos Ex con expresiones, difícil de depurar y prácticamente imposible de reutilizar entre proyectos. Neovim cambió eso en 2021 con la versión 0.5, que convirtió Lua (con LuaJIT) en el lenguaje de primera clase para configuración y plugins. La diferencia es notable: código Lua es código normal, con modules, closures, tablas y todas las herramientas que ya conocemos.

init.lua: el nuevo punto de entrada

En lugar de ~/.config/nvim/init.vim, Neovim carga ~/.config/nvim/init.lua si existe. Desde ahí puedes llamar a cualquier función de la API de Neovim, cargar módulos propios o configurar plugins:

-- ~/.config/nvim/init.lua

-- Opciones del editor (equivalente a :set en init.vim)
vim.opt.number         = true        -- números de línea absolutos
vim.opt.relativenumber = true        -- + relativos
vim.opt.tabstop        = 4
vim.opt.shiftwidth     = 4
vim.opt.expandtab      = true        -- tabs -> espacios
vim.opt.wrap           = false
vim.opt.ignorecase     = true
vim.opt.smartcase      = true        -- mayúsculas anulan ignorecase
vim.opt.termguicolors  = true        -- colores de 24 bits
vim.opt.updatetime     = 250         -- ms para CursorHold
vim.opt.signcolumn     = "yes"       -- columna izquierda siempre visible

-- Variables globales (equivalente a let g:)
vim.g.mapleader      = " "           -- Espacio como leader
vim.g.maplocalleader = "\"

-- Cargar módulos propios
require("mi_config.keymaps")
require("mi_config.lsp")
require("mi_config.plugins")

Keymaps con vim.keymap.set

-- ~/.config/nvim/lua/mi_config/keymaps.lua
local map = vim.keymap.set
local opts = { noremap = true, silent = true }

-- Navegación entre ventanas con Ctrl+hjkl
map("n", "<C-h>", "<C-w>h", opts)
map("n", "<C-j>", "<C-w>j", opts)
map("n", "<C-k>", "<C-w>k", opts)
map("n", "<C-l>", "<C-w>l", opts)

-- Guardar con Ctrl+S en cualquier modo
map({ "n", "i", "v" }, "<C-s>", "<Esc>:w<CR>", opts)

-- Buscar con Telescope
map("n", "<leader>ff", "<cmd>Telescope find_files<CR>",
    { desc = "Buscar ficheros" })
map("n", "<leader>fg", "<cmd>Telescope live_grep<CR>",
    { desc = "Buscar en contenido" })
map("n", "<leader>fb", "<cmd>Telescope buffers<CR>",
    { desc = "Listar buffers" })

-- LSP (se configuran en on_attach, ver más abajo)
map("n", "gd",  vim.lsp.buf.definition,     { desc = "Ir a definición" })
map("n", "K",   vim.lsp.buf.hover,          { desc = "Documentación" })
map("n", "gr",  vim.lsp.buf.references,     { desc = "Referencias" })
map("n", "<leader>rn", vim.lsp.buf.rename,  { desc = "Renombrar símbolo" })
map("n", "<leader>ca", vim.lsp.buf.code_action, { desc = "Acciones de código" })

Gestión de plugins con lazy.nvim

lazy.nvim es el gestor de plugins más usado en 2026. Se instala en el primer arranque y carga los plugins de forma diferida según los eventos, comandos o filetypes que se especifiquen:

-- ~/.config/nvim/lua/mi_config/plugins.lua

-- Bootstrap: instalar lazy.nvim si no existe
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
    vim.fn.system({
        "git", "clone", "--filter=blob:none",
        "https://github.com/folke/lazy.nvim.git",
        "--branch=stable", lazypath,
    })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
    -- Colorscheme
    {
        "folke/tokyonight.nvim",
        lazy = false,
        priority = 1000,
        config = function()
            vim.cmd("colorscheme tokyonight-night")
        end,
    },

    -- Treesitter: resaltado de sintaxis mejorado
    {
        "nvim-treesitter/nvim-treesitter",
        build = ":TSUpdate",
        config = function()
            require("nvim-treesitter.configs").setup({
                ensure_installed = { "lua", "python", "javascript", "rust", "c" },
                highlight = { enable = true },
                indent    = { enable = true },
            })
        end,
    },

    -- Telescope: fuzzy finder
    {
        "nvim-telescope/telescope.nvim",
        dependencies = { "nvim-lua/plenary.nvim" },
        cmd = "Telescope",   -- carga solo cuando se ejecuta :Telescope
    },

    -- LSP
    {
        "neovim/nvim-lspconfig",
        dependencies = {
            "williamboman/mason.nvim",
            "williamboman/mason-lspconfig.nvim",
        },
        config = function()
            require("mi_config.lsp")
        end,
    },
})

Configurar el LSP

-- ~/.config/nvim/lua/mi_config/lsp.lua
require("mason").setup()
require("mason-lspconfig").setup({
    ensure_installed = { "lua_ls", "pyright", "rust_analyzer" }
})

local lspconfig = require("lspconfig")
local on_attach = function(client, bufnr)
    -- Keymaps locales al buffer con LSP activo
    local map = function(keys, func, desc)
        vim.keymap.set("n", keys, func,
            { buffer = bufnr, desc = "LSP: " .. desc })
    end
    map("gd", vim.lsp.buf.definition,   "Ir a definición")
    map("K",  vim.lsp.buf.hover,        "Documentación hover")
    map("gr", vim.lsp.buf.references,   "Referencias")
end

-- Servidor para Lua (lua_ls)
lspconfig.lua_ls.setup({
    on_attach = on_attach,
    settings = {
        Lua = {
            runtime    = { version = "LuaJIT" },
            workspace  = {
                checkThirdParty = false,
                library = vim.api.nvim_get_runtime_file("", true),
            },
            diagnostics = { globals = { "vim" } },
            telemetry   = { enable = false },
        }
    }
})

-- Servidor para Python
lspconfig.pyright.setup({ on_attach = on_attach })

Escribir un plugin Lua propio

Un plugin de Neovim es simplemente un módulo Lua en la ruta de runtime. La estructura mínima es:

-- ~/.config/nvim/lua/mi_config/word_count.lua
local M = {}

function M.contar()
    local lineas = vim.api.nvim_buf_get_lines(0, 0, -1, false)
    local palabras = 0
    for _, linea in ipairs(lineas) do
        for _ in linea:gmatch("%S+") do
            palabras = palabras + 1
        end
    end
    vim.notify("Palabras en el buffer: " .. palabras, vim.log.levels.INFO)
end

-- Crear un comando de usuario
vim.api.nvim_create_user_command("ContarPalabras", M.contar, {})

return M

La apuesta de Neovim por Lua ha generado un ecosistema de plugins de gran calidad: nvim-treesitter para sintaxis, nvim-lspconfig para servidores de lenguaje, telescope.nvim para búsqueda difusa, y decenas más. El lenguaje que se usa para videojuegos y servidores web resulta ser también una herramienta excelente para extender un editor de texto, lo que dice mucho de la versatilidad del diseño de Lua como lenguaje embebido. Si quieres también gestionar paquetes Lua externos desde tus plugins, LuaRocks se integra sin problema con el ecosistema Neovim.

Imagen: Pexels / Godfrey Atima

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP