Quickfix y location list a fondo
La lista que conecta todo el editor: el grep, el compilador, los tests, el LSP y los buscadores desembocan en ella. Poblarla desde cualquier fuente con errorformat, navegarla con los atajos que ya vienen puestos, filtrarla con el plugin que Neovim trae dentro, y editar en masa con :cdo sin apostar.
Una entrada de quickfix es siempre lo mismo: fichero, línea, columna y un texto. Da igual si la puso un grep, el compilador, la suite de tests, un linter o el servidor de lenguaje que montarás en el Nivel 3. Esa uniformidad es todo el invento, y es la razón de que esta lección rente más que ninguna otra del nivel: los mismos seis comandos te sirven para recorrer los errores de compilación, los resultados de una búsqueda o los doscientos avisos de un analizador, hoy y dentro de diez años, con herramientas que aún no existen.
- Distinguir quickfix de location list y saber cuál quieres en cada caso.
- Llenarla desde el grep,
:make, un fichero, un buffer o una expresión cualquiera. - Leer y escribir un
'errorformat', que es el adaptador que hace posible todo lo demás. - Navegarla con los atajos que ya vienen puestos en 0.11, y volver a listas anteriores.
- Filtrarla con
:Cfilter, el plugin que Neovim trae dentro y casi nadie carga. - Editar en masa con
:cdoy:cfdo, y deshacerlo entero si sale mal.
Un formato, muchas fuentes
Quickfix
Una lista por sesión, compartida por todas las ventanas. Sus comandos empiezan por c: :copen, :cnext, :cdo.
Location list
Una lista por ventana. Sus comandos empiezan por l: :lopen, :lnext, :ldo.
La regla práctica: lo global va a la quickfix, lo de un fichero a la location list. Un grep de proyecto es quickfix. Los diagnósticos del fichero que estás mirando son location list, porque quieres que se queden atados a esa ventana mientras haces otra cosa en la de al lado. Todo lo que aprendas de una vale para la otra cambiando la c por la l.
Llenarla
| Fuente | Comando |
|---|---|
| Texto en el proyecto | :grep patrón src/ |
| Regex de Vim sobre ficheros | :vimgrep /patrón/ **/*.lua |
| Errores de compilación o tests | :make |
| La salida de un fichero | :cfile salida.txt |
| El contenido de un buffer | :cbuffer |
| Una lista que construyas tú | :cexpr / :caddexpr |
| Diagnósticos del LSP | :lua vim.diagnostic.setqflist() |
| Buscar en la ayuda | :helpgrep patrón |
:grep ya usa ripgrep, sin que configures nada
Verás muchas guías que empiezan diciéndote que configures 'grepprg' porque «por defecto usa el grep del sistema, que es lento». Eso era verdad en Vim.
Neovim lo detecta solo: si rg está en tu PATH, 'grepprg' ya apunta a él. Compruébalo con :set grepprg?. Solo tendrás que tocarlo si quieres opciones distintas de las que trae.
Lo que sí conviene es la exclamación: :grep! llena la lista sin saltar automáticamente a la primera coincidencia. Casi siempre es lo que quieres —primero mirar qué salió, y luego decidir—, y es la diferencia entre revisar y que te muevan.
:make es mucho más que compilar
:make ejecuta lo que haya en 'makeprg' y mete la salida en la quickfix ya parseada. Y 'makeprg' es una opción cualquiera: puedes apuntarla a tus tests, a tu linter o a tu script de construcción.
:set makeprg=npm\ test:makeA partir de ahí, ]q te lleva de fallo en fallo con el cursor en la línea exacta. Es lo mismo que hace un IDE con su panel de tests, sin panel y sin IDE. En el Nivel 2 esto irá a tu configuración, por tipo de fichero.
:cexpr: cualquier programa, cualquier salida
Cuando la herramienta no encaja en :make, puedes meter su salida directamente:
:cexpr system('mi-linter --formato-corto'):cexpr systemlist('rg --vimgrep TODO'):caddexpr expand('%') . ':' . line('.') . ': anotación mía':cexpr reemplaza la lista y :caddexpr añade. Esa última línea es un truco bonito: te construyes tu propia lista de sitios a los que volver, a mano, mientras lees código.
'errorformat': el adaptador
Aquí está la pieza que hace posible todo lo anterior, y la que casi nadie mira.
Un compilador escribe src/main.c:42:9: error: falta el punto y coma. Otro escribe ERROR en línea 42 de main.c. La quickfix no entiende ninguno de los dos: entiende fichero, línea, columna y texto. 'errorformat' es el patrón que traduce de uno al otro.
| Marca | Captura |
|---|---|
%f |
el nombre del fichero |
%l |
el número de línea |
%c |
la columna |
%m |
el mensaje |
%t |
el tipo: e error, w aviso, i info |
%% |
un % literal |
%*[^:] |
«lo que sea hasta un :», para saltarse trozos |
%-G… |
descarta las líneas que casen |
" para una salida tipo fichero:línea:columna: mensaje:set errorformat=%f:%l:%c:%m" y descartando las líneas de resumen:set errorformat=%f:%l:%c:%m,%-G%.%#Summary%.%#Se declaran varios patrones separados por comas y Neovim prueba en orden. Es la misma sintaxis de 'grepformat', que hace lo propio con la salida del grep.
No hace falta que escribas uno hoy: cada tipo de fichero trae el suyo, y las herramientas modernas casi todas saben emitir el formato de tres campos. Lo que sí hace falta es saber que la pieza existe y qué hace, porque el día que una herramienta tuya escupa errores en un formato raro, la respuesta es esta y no un plugin.
Un formato común convierte N × M integraciones en N + M
Detente en la aritmética de lo que acabas de leer, porque es la razón de que la quickfix lleve treinta años siendo la mejor idea del editor.
Hay muchas herramientas que producen «sitios a los que ir»: compiladores, buscadores, linters, suites de tests, servidores de lenguaje, tus propios scripts. Y hay muchas cosas que quieres hacer con esa información: recorrerla, verla en una ventana, filtrarla, aplicarle un cambio a cada entrada, guardarla para volver después. Sin un formato común, cada pareja necesita su propia integración: un panel de errores del compilador, otro panel distinto para los resultados de búsqueda, otro para los tests, cada uno con sus teclas y sus rarezas. Con seis productores y cinco consumidores son treinta integraciones que alguien tiene que escribir y mantener.
La quickfix hace otra cosa: define una estructura mínima —fichero, línea, columna, texto— y pone un adaptador delante, 'errorformat', cuyo único trabajo es traducir cualquier salida de texto a esa estructura. A partir de ahí, seis más cinco: cada productor solo necesita saber emitir el formato, y cada consumidor solo necesita saber leerlo. Nadie tiene que conocer a nadie.
Y de ahí sale la propiedad que de verdad importa, que es la que notarás tú: funciona con herramientas que no existían cuando se diseñó. ripgrep es de 2016 y la quickfix es de los ochenta; nadie tuvo que añadir «soporte para ripgrep» a Neovim, bastó con que ripgrep supiera imprimir fichero:línea:columna:mensaje. Lo mismo va a pasar con la herramienta que uses en 2030.
El patrón se llama cintura estrecha: un punto del sistema donde deliberadamente hay una sola representación, aunque por arriba y por abajo haya variedad ilimitada. Es lo que hace IP en internet —todo viaja sobre paquetes IP, y por eso Ethernet y la fibra por debajo, y HTTP y el correo por encima, no se conocen entre sí—. Es lo que hace un flujo de texto en las tuberías de Unix. Es lo que hace el DOM entre los lenguajes que lo manipulan y los motores que lo pintan. Y es exactamente lo que hará el protocolo LSP, que verás en el Nivel 19: el mismo cálculo, con editores y lenguajes en vez de productores y consumidores.
Cuando diseñes algo que tenga que hablar con muchas cosas, la pregunta rentable no es cuántas integraciones escribes: es dónde pones la cintura.
Navegarla
| Comando | Acción |
|---|---|
:copen / :cclose |
abrir / cerrar la ventana |
:cwindow |
ábrela solo si hay entradas |
:cnext / :cprev |
siguiente / anterior |
:cfirst / :clast |
primera / última |
:cc {n} |
ir a la entrada n |
:cnfile / :cpfile |
primera entrada del fichero siguiente / anterior |
:chistory |
las listas guardadas |
Pero escribir :cnext a mano no se sostiene, y aquí está la corrección más importante de la lección:
Los atajos ya vienen puestos: no los mapees
Verás en casi todas las guías un bloque de configuración mapeando ]q y [q a :cnext y :cprev. Desde la 0.11 no hace falta: la familia entera de atajos de corchete entró en el núcleo.
| Atajo | Va a |
|---|---|
]q / [q |
entrada siguiente / anterior de la quickfix |
]Q / [Q |
la última / la primera |
]<C-Q> / [<C-Q> |
el fichero siguiente / anterior de la lista |
]l / [l |
lo mismo para la location list |
]L / [L |
última / primera de la location list |
Si añades tus propios mapeos encima estarás pisando los nativos, y perderás las variantes en mayúscula que probablemente no sabías que existían. Compruébalo con :verbose nmap ]q antes de escribir nada.
Lo único que quizá quieras añadir en el Nivel 2 es un zz detrás para que la línea quede centrada al llegar. Eso sí es una preferencia tuya.
Cuando :cnext dice que no hay más y sabes que sí
Cada vez que llenas la lista, la anterior no se pierde: se guarda. Neovim mantiene diez listas de quickfix.
:colder va a la anterior y :cnewer a la siguiente; :chistory te las enseña todas. Si hiciste un grep, luego otro, y el segundo era peor, :colder te devuelve el primero sin repetir la búsqueda.
Filtrar la lista
El grep trajo sesenta resultados y veinte son de un directorio que no te interesa. Reducir la lista antes de actuar sobre ella es la mitad del valor de todo esto.
Lo que NO funciona: borrar líneas con dd
Circula mucho el consejo de «la ventana de quickfix es un buffer, bórrale líneas con dd». No funciona: ese buffer es nomodifiable por defecto y dd te devuelve un E21.
Se puede forzar con :set modifiable, editar y volver a cargar la lista con :cbuffer, pero entonces hay que hacer que 'errorformat' case con el formato de la propia ventana, y es más trabajo del que resuelve.
La respuesta nativa es un plugin que ya tienes instalado y que solo hay que cargar:
:packadd cfilter " una vez por sesión:Cfilter /src\// " deja solo las entradas que casen:Cfilter! /node_modules/ " o quita las que casen:Lfilter /…/ " lo mismo para la location listViene en el paquete dist de Neovim, en pack/dist/opt/cfilter. Compara con el patrón tanto la ruta del fichero como el texto de la entrada, y crea una lista nueva, así que :colder te devuelve la de antes si te pasaste filtrando. Encadenando dos o tres :Cfilter reduces sesenta entradas a las nueve que importan.
En el Nivel 2 pondrás :packadd cfilter en tu configuración y dejará de hacer falta cargarlo a mano.
Edición en masa con :cdo
:cdo ejecuta un comando en cada entrada de la lista.
:grep! miVariable src/ " 1. llenar:copen " 2. REVISAR:Cfilter! /test\// " 2b. y reducir:cdo s/miVariable/miNuevo/ge | update " 3. aplicar y guardarLos cuatro pasos importan, y los dos de en medio son los que distinguen esto de un reemplazo a ciegas.
:cfdo hace lo mismo una vez por fichero en vez de una por entrada. Lo quieres cuando el comando ya afecta al fichero entero: :cfdo %s/x/y/ge | update es mucho más rápido que :cdo si hay muchas coincidencias en pocos ficheros.
Dos detalles que se pagan caros
La bandera e no es opcional. Sin ella, la primera entrada donde el patrón ya no case aborta el :cdo entero y te deja el trabajo a medias, con unos ficheros cambiados y otros no.
El | update tampoco. :cdo cambia los buffers en memoria pero no los guarda; sin él acabas con cuarenta ficheros modificados sin escribir y Neovim te lo recordará uno a uno al cerrar. update guarda solo si hubo cambios, así que es mejor que write para esto.
Deshacer un cambio masivo
:cdo toca muchos buffers, y u deshace solo en el buffer actual. Para revertirlo todo:
:cfdo undo | updateRecorre los ficheros afectados deshaciendo en cada uno. Merece la pena tenerlo apuntado antes de lanzar el primer :cdo, no después.
Y una vuelta a lo anterior
Fíjate en que ya tienes tres formas de describir «el conjunto de sitios sobre los que actuar», y las tres se combinan con lo mismo:
| El conjunto lo eliges con… | Y actúas con… |
|---|---|
| un patrón, dentro de un fichero (lección 1.4) | :g/pat/normal! @a |
| la lista de argumentos (lección 1.1) | :argdo … |
| la quickfix, desde cualquier herramienta | :cdo … |
Es la misma economía de siempre: no aprendes tres sistemas, aprendes una forma de operar y tres formas de describir sobre qué.
Los pasos separados. La revisión intermedia es lo que hace que esto sea un refactor y no una apuesta.
:Cfilter! quita las entradas que casan y crea una lista nueva, así que :colder te devuelve la original.
u solo deshace el buffer actual. :cfdo recorre todos los ficheros afectados.
Neovim guarda las diez últimas listas. :chistory te las enseña.
:make no es solo para compiladores: es para cualquier programa que escriba errores con fichero y línea.
:caddexpr te deja construir tu propia lista de sitios, a mano.
Edición industrial
- Comprueba con
:set grepprg?que ya apunta a ripgrep, sin haber configurado nada. - Haz un
:grep! algoy comprueba que no te ha movido el cursor. Repite sin la exclamación y nota la diferencia. - Recorre la lista entera con
]qy[q, sin haber mapeado nada. Luego prueba]Qy]<C-Q>. - Intenta borrar una entrada con
dddentro de:copeny observa elE21. Después hazlo bien::packadd cfiltery:Cfilter!con un patrón. - Vuelve a la lista sin filtrar con
:colder, y mira:chistory. - Haz un reemplazo en todo el proyecto con
:cdo … | update, y deshazlo entero con:cfdo undo | update. - Apunta
makeprga los tests de un proyecto real y navega los fallos con]q. - Construye una lista a mano: recorre un fichero y añade tres sitios con
:caddexpr, luego:copeny salta entre ellos.