30
loading...
This website collects cookies to deliver better user experience
Read in english here.
:make
, :vimgrep
y :grep
. Adicionalmente, es posible generarla de manera programática usando la funcion setqflist
, lo que nos da una flexibilidad increíble.quickfix window
y el location list
. Estas son básicamente "ventanas" donde se muestra la lista de posiciones. El quickfix window
es "global", es decir que sólo podemos tener una en la sesión activa de vim. Por otro lado, podemos tener múltiples location list
asociados a diferentes ventanas en una misma sesión.:make
, es la manera en la que vim invoca un compilador. ¿El nombre les parece familiar? Eso es porque vim asume que estaremos trabajando con la herramienta make
. Pueden hacer una prueba si lo tienen instalado en su sistema. Crean un archivo llamado Makefile
y colocan lo siguiente:.PHONY: test otra
test:
@echo 'hola'
otra:
@echo 'otra'
No cubriré en detalle cómo funciona make
pero si quieren saber cómo utilizarlo para automatizar tareas pueden leer este artículo (en inglés).
:make
deberían obtener algo como esto.hola
(1 de 1): hola
make
ejecutará la primera "tarea" que vea en nuestro Makefile
. Bien, pero entonces ¿Cómo hacemos para ejecutar otra
? Podemos proveer más argumentos al comando, de esta manera :make [argumento]
. Si intentan ejecutar :make otra
deberían ver esto.otra
(1 de 1): otra
gcc
vamos a ver un ejemplo usando el lenguaje C.hello.c
, en él ponemos el siguiente contenido.#include <stdio.h>
int main() {
printf("Hola, Mundo!\n");
return 0;
}
make
. Así que el siguiente paso será crear un Makefile
..PHONY: run-hello
run-hello:
gcc -Wall -o hello hello.c
./hello
rm ./hello
:make --silent run-hello
. Si todo sale bien vim les mostrará su hola mundo
.Hola, Mundo!
(1 de 1): Hola, Mundo!
hello.c: In function ‘main’:
hello.c:4:28: error: expected ‘;’ before ‘return’
printf("Hola, Mundo!\n")
^
;
return 0;
~~~~~~
make: *** [Makefile:7: run-hello] Error 1
(2 de 8): error: expected ‘;’ before ‘return’
quickfix window
tenemos que ejecutar el comando :copen
, el cual debería mostrarle esto.|| hello.c: In function ‘main’:
hello.c|4 col 28| error: expected ‘;’ before ‘return’
|| printf("Hola, Mundo!\n")
|| ^
|| ;
|| return 0;
|| ~~~~~~
make: *** [Makefile|7| run-hello] Error 1
Para cerrar el quickfix window
se utiliza el comando :cclose
.
hello.c|4 col 28| error: expected ‘;’ before ‘return’
gcc
dice que está nuestro error, así como también el mensaje de error. Es en este punto donde arreglamos el desperfecto e intentamos compilar nuevamente. En su mayoría ese sería el flujo de trabajo que queremos cuando usamos el quickfix list.gcc
? ¿Y si normalmente trabajamos con Node.js
? ¿vim podría manejar eso también? Sí, con algo de ayuda.errorformat
, una variable con la cual podemos especificar el formato del error que genera un comando externo, de esta forma vim puede reconocerlo cuando se encuentre en el quickfix list.node
. Vamos a crear un archivo llamado greeting.js
, en él vamos a tener un "hola mundo" con un error.console.log(greeting);
const greeting = 'Hola, Mundo!';
Makefile
creamos otra tarea.run-greeting:
node ./greeting.js
:make --silent run-greeting
obtendremos este resultado.console.log(greeting);
^
ReferenceError: Cannot access 'greeting' before initialization
at Object.<anonymous> (/tmp/test/greeting.js:1:13)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47
make: *** [Makefile:4: run-greeting] Error 1
set errorformat=%E%.%#ReferenceError:\ %m,%Z%.%#%at\ Object.<anonymous>\ (%f:%l:%c)
%E%.%#ReferenceError:\ %m
%Z%.%#%at\ Object.<anonymous>\ (%f:%l:%c)
ReferenceError
) y dónde está la información sobre la ruta y la ubicación del error. Ya que esas dos piezas de información se encuentran en dos lineas diferentes debo tener dos expresiones separadas por una coma.set errorformat=%E%.%#ReferenceError:\ %m
set errorformat+=%Z%.%#%at\ Object.<anonymous>\ (%f:%l:%c)
\
antes de cada caracter que vim considere especial (como un espacio en blanco) para no generar un conflicto entre la sintaxis de vim y el formato del error. Aún hay otra forma que también podemos usar.let &errorformat =
\ '%E%.%#ReferenceError: %m,' .
\ '%Z%.%#at Object.<anonymous> (%f:%l:%c)'
let
tenemos la posibilidad de crear nuestro formato usando una cadena de texto, sin generar conflictos entre vim y el errorformat
. Para mejorar la legibilidad he puesto cada expresión en su propia linea tomando ventaja del operador .
para concatenar cadenas de texto.:make --silent run-greeting
vim debería llevarnos al lugar exacto donde node
dice que está el error. Y en el quickfix window
debería mostrarnos esto.|| /tmp/test/greeting.js:1
|| console.log(greeting);
|| ^
||
greeting.js|1 col 13 error| Cannot access 'greeting' before initialization
|| at Module._compile (internal/modules/cjs/loader.js:1063:30)
|| at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
|| at Module.load (internal/modules/cjs/loader.js:928:32)
|| at Function.Module._load (internal/modules/cjs/loader.js:769:14)
|| at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
|| at internal/main/run_main_module.js:17:47
|| make: *** [Makefile:4: run-greeting] Error 1
ReferenceError
, y tampoco la línea que va debajo. Esa parte específica del mensaje ha sido reemplazada por esto.greeting.js|1 col 13 error| Cannot access 'greeting' before initialization
errorformat
funcionó.ReferenceError
. Si queremos abarcar otros tipos de errores haríamos algo como esto.let &errorformat =
\ '%E%.%#AssertionError %m,' .
\ '%E%.%#TypeError: %m,' .
\ '%E%.%#ReferenceError: %m,' .
\ '%E%.%#SyntaxError: %m,' .
\ '%E%.%#RangeError: %m,' .
\ '%Z%.%#at Object.<anonymous> (%f:%l:%c),' .
\ '%-G%.%#'
%
. Vamos a dar un repaso sobre esos "tokens" especiales.%f
: Indica la ubicación de la ruta del archivo donde se encuentra el error.
%l
: Indica la línea donde se encuentra el error.
%c
: Indica la columna donde se encuentra el error.
%m
: Se usa para indicar la ubicación del contenido del mensaje. En nuestro ejemplo lo usamos para asignar la cadena de texto que viene inmediatamente después del tipo de error.
%E
: Indica el inicio de un mensaje de error que abarca múltiples líneas. La letra E
le dice a vim que esto es un error. También podemos señalar mensajes de advertencia (%W
), mensajes informativos (%I
) ó generales (%G
).
%Z
: Indica que es la última línea del mensaje.
%.%#
: Esto es un comodín, basícamente coincide con todo. En otras palabras, significa "cualquier cosa." Por ejemplo, la expresión %Z%.%#at Object.<anonymous> (%f:%l:%c)
puede leerse de la siguiente forma: La última línea de este mensaje (%Z
) puede comenzar con cualquier cosa (%.%#
) seguido de at Object.<anonymous>
y entre paréntesis el archivo (%f
), un número de línea (%l
) y la columna (%c
).
%-
: Le indica a vim que no debe incluir este fragmento en el quickfix window
. Entonces la expresión %-G
se puede leer como "no incluyas este mensaje." En nuestro ejemplo usamos %-G%.%#
que basícamente le dice a vim "ignora todo lo demás." Ya que %.%#
es una expresión que coincide con todo lo colocamos de último.
errorformat
pueden ejecutar el comando :help errorformat
.make
. No tiene que ser así. Podemos cambiar el comando externo que vim ejecuta cuando invocamos :make
.node
directamente, para lograr nuestro objetivo sólo tenemos que modificar la variable makeprg
.set makeprg=node
let &makeprg = 'node'
:make --silet run-greeting
podemos ejecutar :make ./greeting.js
ó :make %
si estamos editando greeting.js
.:grep
y :vimgrep
, ellos pueden crear un quickfix list con los resultados de una búsqueda.:vimgrep
podemos buscar un patrón (una expresión regular) en múltiples archivos. Se usa de la siguiente manera::vimgrep /<patrón>/ <archivos>
/
no son estrictamente obligatorios, pero es una buena idea incluirlos si el patrón que están buscando contiene algún caracter que cause conflicto con la sintaxis de vim. Por ejemplo::vimgrep /create table/ db/**/*.sql
create table
en un directorio llamado db
, y seleccionando específicamente todos los archivos que terminen con la extensión .sql
./
. Pueden ser cualquier caracter que vim no considere un "identificador," pueden encontrar más detalles en la documentación de vim :help isident
. Esto puede resultar útil si /
ya se encuentra en nuestro patrón. Si estuviéramos buscando una ruta en nuestro código podríamos hacer algo así.:vimgrep #/home/user/code# scripts/*.sh
wildignore
. Algo así.:set wildignore=*/cache/*,*/tmp/*
cache
y tmp
de cualquier expansión de ruta o autocompletado. Por ejemplo, al buscar, si usamos un patrón como este **/*.js
vim no incluirá ningún directorio que contenga en su ruta /cache/
o /tmp/
. Entonces, :vimgrep
no buscará en esos directorios porque técnicamente nunca los recibe como parámetro. Quiere decir también que esta opción no es específica para :vimgrep
, también puede afectar otros comandos.git
.:vimgrep /function/ `git ls-files`
:vimgrep
cualquier comando externo que nos devuelva una lista de archivos es válido.make
. Ahora vamos a explorar el comando :grep
, que es la manera de vim de integrarse con la herramienta de búsqueda grep
. En su mayoría funciona igual que :vimgrep
con la diferencia de que todos los argumentos deben ser compatibles con la sintaxis de grep
. Por ejemplo.:grep -r "create table" db/**/*.sql
/
sino comillas dobles y le añado el parámetro -r
(para habilitar la búsqueda recursiva en directorios), esto es porque el comando :grep
lo que hace es invocar un "shell" no interactivo para ejecutar el programa grep
y le pasa todos los argumentos tal cual como los escribimos.:grep
por encima de :vimgrep
? Resulta que :grep
es más rápido y eficiente que :vimgrep
. Entonces, :grep
es la mejor opción si tienen que buscar en muchos archivos o archivos que son tienen mucho contenido. :vimgrep
sobre :grep
? No mucho diría yo. Lo primero sería que la sintaxis para las expresiones regulares es la misma que usa vim, si están familiarizados con esa sintaxis pueden tomar ventaja de sus bondades. También, :vimgrep
funciona de manera consistente en todas las plataformas.grep
? :grep
con su configuración por defecto no funcionaría. Pero al igual que :make
, con :grep
podemos modificar el comando externo que vim ejecuta, usando la variable grepprg
. Digamos que no tenemos grep
sino ripgrep instalado en nuestro sistema, en ese caso podremos hacer esto.set grepprg=rg\ --vimgrep\ --smart-case\ --follow
rg
con los parámetros especificados para realizar las búsquedas cuando nosotros usemos :grep
.Rg
y Ag
. Después de realizar una búsqueda y se encuentren con su lista de resultados, pueden seleccionar múltiples items (con la tecla tab
para seleccionar uno o Alt + a
para seleccionar todos) y presionar enter
. Eso es todo. Después el quickfix list debería tener todo lo que ustedes seleccionaron. Esto resulta muy útil cuando quieren ejecutar comandos sólo en unas partes del código usando :cdo
.:cdo
, voy a mostrarles una de las cosas más útiles que podemos hacer ese comando: buscar y reemplazar un patrón en múltiples archivos. Una tarea bastante común, y seguramente se han preguntado cómo pueden hacerlo usando vim. La respuesta a eso es el quickfix list y el comando :cdo
.:grep
, :vimgrep
o cualquier otro comando que pueda generar un quickfix list.:copen
.:cdo
para ejecutar una acción sobre cada item en el quickfix list. En este caso la acción que queremos hacer es sustituir y en vim lo hacemos con esta sintaxis s/{patrón}/{reemplazo}
. :vimgrep node **/*.js
node
con deno
y para eso usamos este comando.:cdo s/node/deno/ | update
s/node/deno | update
en cada item de nuestro quickfix list. En realidad :cdo
puede ejecutar cualquier comando válido de vim. En nuestro ejemplo en concreto lo usamos para hacer dos cosas, reemplazar el texto node
y guardar los cambios en el archivo.errorformat
. Entonces en su .vimrc
pueden colocar algo como esto.set errorformat+=%f\|%l\ col\ %c\|%m
:set modifiable
:cdo
todavía. Tienen que asegurarse de crear un quickfix list basado en los resultados que quieren. Entonces, luego de hacer sus modificaciones en el buffer ejecutan este comando.:cgetbuffer
:cclose
y luego :copen
.Ver en asciinema.
.vimrc
.command! -nargs=+ Grep execute 'silent grep! <args>' | copen
grep
por vimgrep
si eso desean. Lo importante aquí es que ahora podrán invocar el comando :Grep
(con G
mayúscula) para realizar sus búsquedas." Ir a la ubicación anterior
nnoremap [q :cprev<CR>
" Ir a la siguiente ubicación
nnoremap ]q :cnext<CR>
" Mostrar el quickfix window
nnoremap <Leader>co :copen<CR>
" Ocultar el quickfix window
nnoremap <Leader>cc :cclose<CR>
augroup quickfix_mapping
autocmd!
autocmd filetype qf setlocal errorformat+=%f\|%l\ col\ %c\|%m
augroup END
function! QuickfixMapping()
" Ir a la ubicación anterior y mantenerse en el quickfix window
nnoremap <buffer> K :cprev<CR>zz<C-w>w
" Ir a la siguiente ubicación y mantenerse en el quickfix window
nnoremap <buffer> J :cnext<CR>zz<C-w>w
" Haz que el quickfix list sea modificable
nnoremap <buffer> <leader>u :set modifiable<CR>
" Actualiza el quickfix window
nnoremap <buffer> <leader>w :cgetbuffer<CR>:cclose<CR>:copen<CR>
" Buscar y reemplazar
nnoremap <buffer> <leader>r :cdo s/// \| update<C-Left><C-Left><Left><Left><Left>
endfunction
augroup quickfix_mapping
autocmd!
autocmd filetype qf call QuickfixMapping()
augroup END
" Comando Grep que busca y abre el quickfix window
command! -nargs=+ Grep execute 'silent grep! <args>' | copen
" Ir a la ubicación anterior
nnoremap [q :cprev<CR>
" Ir a la siguiente ubicación
nnoremap ]q :cnext<CR>
" Mostrar el quickfix window
nnoremap <Leader>co :copen<CR>
" Ocultar el quickfix window
nnoremap <Leader>cc :cclose<CR>
function! QuickfixMapping()
" Ir a la ubicación anterior y mantenerse en el quickfix window
nnoremap <buffer> K :cprev<CR>zz<C-w>w
" Ir a la siguiente ubicación y mantenerse en el quickfix window
nnoremap <buffer> J :cnext<CR>zz<C-w>w
" Haz que el quickfix list sea modificable
nnoremap <buffer> <leader>u :set modifiable<CR>
" Actualiza el quickfix window
nnoremap <buffer> <leader>w :cgetbuffer<CR>:cclose<CR>:copen<CR>
" Buscar y reemplazar
nnoremap <buffer> <leader>r :cdo s/// \| update<C-Left><C-Left><Left><Left><Left>
endfunction
augroup quickfix_mapping
autocmd!
" Asignar los atajos específicos para el quickfix window
autocmd filetype qf call QuickfixMapping()
" Agregar formato para modificar el quickfix list
autocmd filetype qf setlocal errorformat+=%f\|%l\ col\ %c\|%m
augroup END
:grep
, :vimgrep
e incluso :make
sin tener que crear otros comandos. Pero si queremos disfrutar de algunas de sus bondades tenemos que crear nuestros propios atajos.nmap <Leader>cc <Plug>(qf_qf_toggle)
" Ir a la ubicación anterior
nmap [q <Plug>(qf_qf_previous)zz
" Ir a la siguiente ubicación
nmap ]q <Plug>(qf_qf_next)zz
function! QuickfixMapping()
" Ir a la ubicación anterior y mantenerse en el quickfix window
nmap <buffer> K <Plug>(qf_qf_previous)zz<C-w>w
" Ir a la siguiente ubicación y mantenerse en el quickfix window
nmap <buffer> J <Plug>(qf_qf_next)zz<C-w>w
endfunction
augroup quickfix_mapping
autocmd!
autocmd filetype qf call QuickfixMapping()
augroup END
vim-qf
no dan error cuando llegamos al final de la lista. Es decir que si estamos visualizando el último item y usamos ]q
para ir al siguiente, en lugar de tener un error este comando nos lleva al primer item del quickfix list../test dir/a-file.txt|1 col 11| nnoremap <leader>f :FZF
./test dir/a-file.txt|2 col 11| nnoremap <leader>ff :FZF<CR>
./test dir/a-file.txt|3 col 11| nnoremap <leader>fh :History<CR>
./test dir 2/another-file.txt|1 col 11| nnoremap <leader>? :Maps<CR>
./test dir 2/another-file.txt|2 col 11| nnoremap <leader>bb :Buffers<CR>
- ./test dir/a-file.txt|1 col 11| nnoremap <leader>f :FZF
+ ./test dir/a-file.txt|1 col 11| nnoremap <AAA>f :FZF
./test dir/a-file.txt|2 col 11| nnoremap <leader>ff :FZF<CR>
./test dir/a-file.txt|3 col 11| nnoremap <leader>fh :History<CR>
- ./test dir 2/another-file.txt|1 col 11| nnoremap <leader>? :Maps<CR>
+ ./test dir 2/another-file.txt|1 col 11| nnoremap <BBB>? :Maps<CR>
./test dir 2/another-file.txt|2 col 11| nnoremap <leader>bb :Buffers<CR>
:write
(o :w
para abreviar) se quedarán reflejados en sus lugares respectivos en cada archivo. Esto resulta sumamente poderoso porque ahora la flexibilidad del quickfix list está limitada a nuestro conocimiento sobre vim../test dir/a-file.txt|1 col 11| nnoremap <leader>f :FZF
./test dir/a-file.txt|3 col 11| nnoremap <leader>fh :History<CR>
./test dir 2/another-file.txt|1 col 11| nnoremap <leader>? :Maps<CR>
:%s/leader/localleader/g
./test dir/a-file.txt|1 col 11| nnoremap <localleader>f :FZF
./test dir/a-file.txt|3 col 11| nnoremap <localleader>fh :History<CR>
./test dir 2/another-file.txt|1 col 11| nnoremap <localleader>? :Maps<CR>
:write
:make
para invocar cualquier compilador o comando externo que pueda ejecutar nuestro código y darnos mensajes de error. Tenemos las herramientas para hacer que vim entienda un mensaje de error y pueda incorporar la información relevante al quickfix list.:vimgrep
y :grep
para buscar patrones en múltiples archivos. También pudimos explorar diferentes formas de reemplazar el texto que estamos buscando, con casos simples y otros un poco más complejos. Con estos casos pudimos aprender cómo hacer la sustitución sin usar plugins y también cómo hacerlo con la ayuda de un par de plugins..vimrc
para hacer que la experiencia al usar el quickfix list sea más placentera e intuitiva.