wandres.dev
ARQUITECTO · Distribuciones y LazyVim

Personalizar LazyVim sin romperlo

Override de plugins y opciones, tus propios keymaps, añadir y desactivar plugins, cambiar el tema y deshacer los choques de teclas que la distro trae de fábrica.

⏱ 16 min

Aquí está la habilidad clave del Arquitecto: tomar una base ajena y hacerla tuya sin romperla. LazyVim está diseñada para que la modifiques con elegancia — override, no parche. Estas son las operaciones que necesitas, incluida la menos glamurosa y la más urgente: recuperar las teclas que la distro repartió por ti.

🎯 Al terminar esta lección sabrás
  • Sobreescribir las opts de un plugin de LazyVim.
  • Entender el caso especial de Treesitter, donde casi todo el mundo copia API muerta.
  • Añadir tus propios plugins y desactivar los que no quieras.
  • Poner tus opciones y keymaps que ganan a los de la distro.
  • Resolver el choque de s, S y <C-Space> que LazyVim te deja servido.
  • Cambiar el tema (colorscheme).

La regla de oro: override, no editar la distro

Nunca edites los archivos internos de LazyVim. En su lugar, crea un archivo en lua/plugins/ con una spec del mismo plugin: LazyVim fusiona tu configuración con la suya. Tus cambios sobreviven a las actualizaciones.

1) Sobreescribir las opciones de un plugin

Devuelve una spec con el mismo plugin y tus opts. Se hace un deep merge con las de LazyVim:

return {
  -- Referencia el plugin por su nombre corto: si el repo cambia de owner,
  -- tu override sigue apuntando al sitio correcto.
  {
    "gitsigns.nvim",
    opts = {
      signs = { add = { text = "┃" }, change = { text = "┃" } },
    },
  },
}

¿Necesitas lógica, no solo fusionar? Usa opts como función y recibes las opciones actuales para modificarlas:

return {
  {
    "nvim-lualine/lualine.nvim",
    opts = function(_, opts)
      -- modifica lo que LazyVim ya configuró
      table.insert(opts.sections.lualine_x, "  " .. os.date("%H:%M"))
      return opts
    end,
  },
}

2) El caso especial: Treesitter

⚠️
La API que copias de internet no existe

La rama main de nvim-treesitter es una reescritura incompatible. El módulo nvim-treesitter.configs desapareció, y con él highlight, indent e incremental_selection. El plugin hoy hace una sola cosa: instalar parsers y aportar queries, con require("nvim-treesitter").install { "lua", "rust" }. El resaltado lo enciende vim.treesitter.start() desde un autocomando FileType, y el plegado sale de vim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()' con foldmethod = 'expr'. Si un tutorial te enseña require("nvim-treesitter.configs").setup(...), está escrito para la rama congelada.

Dicho eso, dentro de LazyVim escribes ensure_installed, y aquí es donde se confunde casi todo el mundo:

return {
  {
    "nvim-treesitter/nvim-treesitter",
    opts = { ensure_installed = { "swift", "zig" } }, -- se AÑADEN a las de LazyVim
  },
}

ensure_installed no es una opción del plugin: es una opción de la spec de LazyVim, que la lee, la extiende como lista y luego llama por ti a la API real de la rama main. Por eso funciona aquí y por eso no hace absolutamente nada en la config a mano que montaste en el Nivel 2 — allí llamas tú directamente a require("nvim-treesitter").install.

ℹ️
Lo que NO debes tocar de esa spec

No cambies el branch ni intentes reconfigurar cuándo carga: LazyVim ya gobierna esa spec en la rama main y sabe lo que hace. Limítate a añadir lenguajes por ensure_installed. Y si algún día sacas Treesitter de la distro, recuerda que fuera de aquí la rama main no admite carga perezosa: va con lazy = false, branch = "main" y build = ":TSUpdate".

3) Añadir tus propios plugins

Igual que en tu config a mano: la spec en lua/plugins/, y se suma a la base de LazyVim.

folke/zen-mode.nvim
return {
  {
    "folke/zen-mode.nvim",
    cmd = "ZenMode",
    opts = {},
    keys = {
      { "<leader>uz", "<cmd>ZenMode<cr>", desc = "Modo zen" },
    },
  },
}
vim.pack.add({ { src = "https://github.com/folke/zen-mode.nvim" } })

require("zen-mode").setup({})
vim.keymap.set("n", "<leader>uz", "<cmd>ZenMode<cr>", { desc = "Modo zen" })

Dentro de una config de LazyVim lo normal es la vía lazy. La de vim.pack te sirve si lo montas fuera de la distro o si mantienes también una config propia.

4) Desactivar un plugin de LazyVim

Ponle enabled = false:

return {
  { "folke/flash.nvim", enabled = false }, -- fuera flash, prefiero otra cosa
}

5) Opciones y keymaps que ganan

LazyVim carga tus lua/config/options.lua y keymaps.lua después de los suyos, así que lo tuyo prevalece:

-- se fusiona con las opciones de LazyVim; lo tuyo manda
vim.opt.relativenumber = true
vim.opt.scrolloff = 10
vim.g.snacks_animate = false
local map = vim.keymap.set
map("n", "<leader>ww", "<cmd>w<cr>", { desc = "Guardar rápido" })

-- ¿quitar un keymap que pone LazyVim? bórralo:
-- vim.keymap.del("n", "<leader>xx")
💡
Descubre los atajos de LazyVim

LazyVim trae which-key configurado: pulsa <leader> y espera para ver TODO su mapa de atajos, con sus grupos (<leader>f buscar, <leader>g git, <leader>c código…). Antes de crear un keymap, comprueba si ya existe uno; así no duplicas ni pisas nada sin querer.

6) El reparto de teclas: el conflicto que viene de fábrica

Esta es la personalización que de verdad importa, porque no falla con un error sino con silencio. Empieza por el diagnóstico, siempre:

:map s
:map <C-Space>
:map <Tab>

En una LazyVim recién instalada, :map s te responde que s y S son de flash.nvim, y :map <C-Space> te dice que esa tecla también. La buena noticia es que en lo primero LazyVim coincide con esta guía; la mala, que lo segundo hay que arreglarlo.

Lo que LazyVim hace bien: s para flash, gs para surround

Es exactamente el reparto de la lección 2.3, así que aquí no tienes que tocar nada. LazyVim cede s y S a flash porque un salto es una moción y merece dos teclas, y en su extra coding.mini-surround mueve surround al prefijo gs (gsa, gsd, gsr), que nativamente significa “dormir N segundos” y por tanto está libre. Se pierden s y S de Vim, que son cl y cc — coste casi nulo.

El choque aparece cuando añades mini.surround con sus valores por defecto encima, por ejemplo trayendo tu archivo del Nivel 2 sin adaptarlo. Entonces hay dos plugins reclamando s y gana el que cargue último. Si eso te pasa, la solución es la misma que ya usas: fijar el prefijo gs explícitamente.

return {
  {
    "mini.surround",
    opts = {
      mappings = {
        add = "gsa",        -- añadir delimitadores
        delete = "gsd",     -- borrarlos
        replace = "gsr",    -- cambiarlos
        find = "gsf",
        find_left = "gsF",
        highlight = "gsh",
      },
    },
  },
}

O más simple: activa el extra con :LazyExtras y no escribas nada, porque hace justo esto.

Lo que sí hay que quitarle: <C-Space>

Esta no se negocia. En este track <C-Space> está reservado para el autocompletado —el nativo de 0.12 se enciende con vim.o.autocomplete y se afina con vim.opt.completeopt— y <Tab> para moverse por su menú. LazyVim se la da a flash, y el síntoma es que tu completado deja de responder sin dar ningún error.

-- Ctrl-Espacio es del autocompletado. Punto.
pcall(vim.keymap.del, { "n", "x", "o" }, "<C-Space>")

Si además prefieres quedarte solo con los mapeos de flash que tú elijas y descartar el resto de los suyos (r, R y <C-Space> incluidos), pasa keys como función: lazy descarta las teclas heredadas y usa únicamente lo que devuelvas.

return {
  "folke/flash.nvim",
  -- keys como función DESCARTA las de LazyVim (s, S, r, R y Ctrl-Espacio)
  keys = function()
    return {
      { "s", mode = { "n", "x", "o" }, function() require("flash").jump() end,       desc = "Flash: saltar" },
      { "S", mode = { "n", "x", "o" }, function() require("flash").treesitter() end, desc = "Flash: nodo" },
    }
  end,
}
⚠️
La comprobación que cierra el asunto

Reinicia y vuelve a ejecutar :map s y :map <C-Space>. :map s debe mostrar un solo dueño, flash, y :map <C-Space> debe salir vacío en modo normal. Si :map s te lista dos entradas, todavía tienes la bomba puesta: solo que hoy ha cargado en el orden que te favorece.

7) Cambiar el tema

LazyVim usa Tokyo Night por defecto. Para poner Catppuccin (o cualquier otro), instala el tema y dile a LazyVim que lo use:

catppuccin/nvim
return {
  { "catppuccin/nvim", name = "catppuccin", opts = { flavour = "mocha" } },
  {
    "LazyVim/LazyVim",
    opts = { colorscheme = "catppuccin" },
  },
}
vim.pack.add({ { src = "https://github.com/catppuccin/nvim" } })

require("catppuccin").setup({ flavour = "mocha" })
vim.cmd.colorscheme("catppuccin")

Dentro de LazyVim usa la vía lazy: la distro necesita saber el nombre del tema para cargarlo en el momento correcto.

La mentalidad override

Todo en LazyVim se personaliza declarando lo que quieres distinto, no reescribiendo lo que ya funciona. Un archivo por cambio, cada uno diciendo “de este plugin, cambia esto”. Tu carpeta lua/plugins/ se convierte en la lista de tus decisiones sobre la base, y cuando la distro se actualice tus overrides seguirán en pie porque nunca tocaste sus tripas. Pero fíjate en cuál ha sido el override más importante de esta lección: no una opción bonita, sino repartir el teclado. Una config con cuarenta plugins tiene un presupuesto de teclas finito, y el conflicto no avisa con un error — una función simplemente deja de responder y crees que el plugin está roto. Por eso :map y :checkhealth son las dos primeras cosas que miras cuando algo “no funciona”.

⚔️ Hazla tuya
  1. Ejecuta :map s y :map <C-Space> y anota quién los tiene ahora mismo.
  2. Elige la opción A o la B y aplícala. Vuelve a ejecutar :map s: debe haber un solo dueño.
  3. Borra el mapeo de <C-Space> y confirma que tu autocompletado responde otra vez.
  4. Cambia el tema a Catppuccin con un override.
  5. Añade un plugin nuevo (por ejemplo zen-mode) en lua/plugins/.
  6. Desactiva un plugin que no uses con enabled = false.
  7. Añade swift a ensure_installed de Treesitter y comprueba que se suma a los de LazyVim en vez de sustituirlos.