Git, formateo, linting y debugging
Cierra el IDE: gitsigns y lazygit para Git, conform.nvim para formatear al guardar, nvim-lint para linters, y nvim-dap para depurar con breakpoints.
Un IDE no es solo LSP. Es también ver los cambios de Git en el margen, formatear al guardar, avisar de problemas de estilo y poner breakpoints. Vamos a montar esas cuatro piezas.
- Git integrado: gitsigns (hunks en el margen) y lazygit (TUI completa).
- Formateo automático al guardar con conform.nvim.
- Linting con nvim-lint.
- Debugging real con nvim-dap y su UI.
Git dentro de Neovim
gitsigns: el margen que lo cuenta todo
Muestra líneas añadidas, modificadas y borradas en la columna de signos, y permite hacer stage, reset y ver el blame sin salir del buffer.
next_hunk y prev_hunk están deprecados
Casi todo lo escrito antes de 2025 usa gs.next_hunk() y gs.prev_hunk(). La documentación oficial de gitsigns los marca como deprecados a favor de gs.nav_hunk("next") y gs.nav_hunk("prev"), que además aceptan "first" y "last". En la misma lista de deprecados está undo_stage_hunk: hoy se deshace llamando a stage_hunk sobre un signo ya en stage. Y ojo con preview_hunk: no recibe argumentos; la variante flotante en línea es preview_hunk_inline.
El objeto de texto “un hunk”, y por qué no lo mapeamos a ih
Falta una pieza obvia: poder decir “borra este hunk” con dih. gitsigns trae la función (select_hunk) pero no la mapea, así que la tecla la eliges tú — y aquí hay una trampa que se explica sola en cuanto la ves.
Si mapeas ih y además usas mini.ai (lo activaste en la lección 3.3), tienes dos dueños del prefijo i. mini.ai mapea la i a secas en modo Visual y operador, y luego lee el siguiente carácter para saber qué objeto quieres. gitsigns mapearía ih, dos caracteres. Vim resuelve esa ambigüedad esperando: al pulsar i no sabe si vas a completar ih o si i va solo, así que se queda quieto hasta timeoutlen. Si escribes rápido gana gitsigns; si dudas medio segundo gana mini.ai, que buscará texto entre dos caracteres h. El mismo tecleo hace dos cosas distintas según lo rápido que vayas, y eso es peor que no tener el objeto.
La salida limpia es que el prefijo i tenga un solo dueño: registra el hunk dentro de mini.ai, que ya lo gobierna.
require("mini.ai").setup({ custom_textobjects = { -- Ahora "ih" y "ah" pasan por mini.ai, que es quien manda en la i. h = function() local ok, gs = pcall(require, "gitsigns") if not ok then return nil end local hunks = gs.get_hunks() if not hunks then return nil end local fila = vim.fn.line(".") for _, h in ipairs(hunks) do local ini = h.added.start local fin = ini + math.max(h.added.count, 1) - 1 if fila >= ini and fila <= fin then return { from = { line = ini, col = 1 }, to = { line = fin, col = math.max(#vim.fn.getline(fin), 1) }, } end end end, },})Si no usas mini.ai, nada de esto aplica: mapea ih directamente a gs.select_hunk en los modos o y x y listo, porque entonces el prefijo i no tiene competencia.
Cuatro fallos que no dan error, todos en el mismo plugin
Merece la pena mirar juntos los cuatro detalles de esta sección, porque son la misma familia de bug y la vas a reencontrar en cada plugin que instales. Un signo de borrado vacío no te avisa de que está vacío: simplemente no ves nunca las líneas eliminadas y acabas creyendo que gitsigns no las detecta. stage_hunk sin rango en Visual no da error: hace stage del hunk entero cuando tú seleccionaste tres líneas, y solo lo descubres al revisar el commit. Un salto que no pasa por make_repeatable_move no falla: es que ; no lo repite, y lo achacas a que “los repeats no funcionan con gitsigns”. Y el objeto ih compitiendo con mini.ai es el peor de los cuatro, porque el resultado depende de tu velocidad de tecleo. Ninguno produce un mensaje en :messages, ninguno aparece en :checkhealth, y los cuatro se manifiestan como “este plugin va raro”. La lección transferible: cuando copies una configuración de internet, la pregunta útil no es si funciona —casi siempre funciona en el caso feliz— sino qué caso límite no está cubierto: el modo Visual, el rango vacío, el prefijo compartido, el signo que no se ve. Ahí es donde vive el noventa por ciento del tiempo que la gente pierde con su editor.
lazygit: la TUI que lo cambia todo
Para commits, ramas, rebase interactivo y resolución de conflictos, nada supera a lazygit. Lo abrimos flotante desde Neovim (requiere tener lazygit instalado, lo hiciste en la lección 0.2):
Flujo Git de nivel dios
Usa gitsigns para el trabajo fino (stage por hunk, blame, saltar entre cambios) y lazygit (<leader>gg) para todo lo demás: commits, ramas, rebase. Es la combinación que usan la mayoría de devs de Neovim en 2026.
La otra historia: el árbol de deshacer, dibujado
Git guarda la historia que decides publicar; el árbol de deshacer (lección 1.8) guarda toda la que ha pasado por el buffer, ramas incluidas. Allí aprendiste a navegarlo a ciegas con g-, g+ y :undolist; esta es la deuda que quedó pendiente: un plugin que lo dibuje.
Panel lateral con el árbol completo: cada rama, su marca de tiempo y el diff de cada estado al pasar por él. Mapea :UndotreeToggle a <leader>uu y úsalo cuando g- te haya llevado a una rama y no sepas dónde estás. Con undofile activado (lección 1.8), el árbol sobrevive al cierre y esto se convierte en un time-machine gratuito por fichero.
Formateo al guardar: conform.nvim
conform.nvim ejecuta el formateador correcto para cada lenguaje. Necesita los binarios instalados (stylua, clang-format, prettierd…): con :Mason o con el gestor de paquetes de tu sistema, como prefieras.
Comprueba qué formateador se está usando
Ejecuta :ConformInfo: te dice qué formateadores hay configurados para el buffer actual, cuáles encontró en el PATH y dónde está el log. Si un binario falta, conform lo omite en silencio para ese lenguaje; lsp_format = "fallback" hace que entonces formatee el propio servidor LSP, que en Neovim 0.12 basta para muchos lenguajes.
Linting con nvim-lint
El LSP ya da muchos diagnósticos, pero para linters de estilo (eslint_d, shellcheck, clippy) nvim-lint los ejecuta y los muestra como diagnósticos:
Debugging con nvim-dap
nvim-dap implementa el Debug Adapter Protocol: breakpoints, step, inspección de variables. Le añadimos una UI y virtual text.
Falta el adaptador. codelldb es el de los lenguajes nativos —C, C++, Rust, Swift— y lo instalas con :Mason o con tu gestor de paquetes; con jay-babu/mason-nvim-dap.nvim puedes automatizarlo (ensure_installed = { "codelldb" }). En cada lección de lenguaje configuraremos su dap.configurations específico.
Las teclas que NO puedes usar aquí
Fíjate en que todos los atajos de esta lección viven en <leader> o en los prefijos ] y [. Es deliberado. En este track, <C-Space> está reservado para el autocompletado —el nativo se enciende con vim.o.autocomplete— y <Tab> para moverse por su menú;Y s y S son de flash.nvim, con mini.surround en el prefijo gs (lección 3.3). Antes de inventarte una tecla, pregúntale a Neovim: :map <leader>d, :map s, :map <C-Space>. Si la respuesta no está vacía, elige otra.
El print debugging tiene los días contados
Con <leader>db pones un breakpoint, <leader>dc arranca, y la UI (<leader>du) te muestra variables, call stack y scopes en vivo. Aprender a depurar de verdad dentro de Neovim, en lugar de sembrar print, es uno de los saltos de productividad más grandes que darás.
El IDE completo
- Añade los cinco archivos (
git,lazygit,format,lint,dap) y reinicia. - Modifica un archivo versionado: mira los signos de gitsigns y salta con
]h. - Abre
<leader>gg(lazygit), haz un commit de prueba. - Desordena la indentación y guarda: conform debería arreglarla. Confirma con
:ConformInfoquién lo hizo. - Pon un breakpoint con
<leader>db(lo usarás de verdad en las lecciones de lenguaje). - Prueba el objeto de texto
ih:vihselecciona el hunk bajo el cursor,dihlo revierte a lo bruto.
Con esto, Neovim ya es un IDE. Ahora lo afinamos para cada lenguaje.