Aprende-Vim/cap26_condicionales_y_bucles_vimscript.md

516 lines
13 KiB
Markdown
Raw Normal View History

2021-04-21 04:41:53 +02:00
# Capítulo 25: Condicionales y bucles en Vimscript
2021-04-12 11:45:10 +02:00
Después de aprender cuales son los tipos básicos de datos que existen en Vimscript, el siguiente paso es aprender cómo combinarlos para empezar a escribir un programa básico. Un programa básico consiste en condicionales y bucles.
2021-04-11 19:19:19 +02:00
En este capítulo, aprenderás cómo utilizar los tipos de datos de Vimscript para escribir esos condicionales y bucles.
2021-04-11 19:19:19 +02:00
## Operadores relacionales
2021-04-11 19:19:19 +02:00
Los operadores relacionales de Vimscript son similares a los que que existen en la mayoría de lenguajes de programación:
```text
a == b igual a
a != b no igual a
a > b mayor que
a >= b mayor o igual que
a < b menor que
a <= b menor o igual que
```
2021-04-11 19:19:19 +02:00
Por ejemplo:
```text
:echo 5 == 5
:echo 5 != 5
:echo 10 > 5
:echo 10 >= 5
:echo 10 < 5
:echo 5 <= 5
```
Recuerda que las _strings_ o cadenas son forzadas a números en una expresión aritmética. Aquí Vim fuerza las cadenas a números en una expresión de igualdad."5foo" es forzado a 5 \(verdadero\):
```text
:echo 5 == "5foo"
" devuelve true
```
2021-06-15 19:18:09 +02:00
También recuerda que si comienzas una cadena con un carácter no numérico como "foo5", la cadena es convertida al número 0 \(falso\).
```text
echo 5 == "foo5"
" devuelve false
```
2021-04-11 19:34:55 +02:00
### Operadores lógicos para cadenas
2021-04-11 19:34:55 +02:00
Vim tiene más operadores relacionales para comparar cadenas:
```text
a =~ b
a !~ b
```
2021-04-11 19:34:55 +02:00
Por ejemplo:
```text
2021-04-11 19:34:55 +02:00
let str = "abundante desayuno"
2021-04-11 19:34:55 +02:00
echo str =~ "abundante"
" devuelve true
2021-04-11 19:34:55 +02:00
echo str =~ "cena"
" devuelve false
2021-04-11 19:34:55 +02:00
echo str !~ "cena"
" devuelve true
```
El operador `=~` realiza una coincidencia de expresiones regulares contra la cadena dada. En el ejemplo anterior, `str =~ "hearty"` devuelve verdadero porque `str` _contiene_ el patrón "abundante". Siempre puedes utilizar `==` o `!=`, pero al usarlos comparará la expresión contra la cadena entera. `=~` o `!~` son unas elecciones más flexibles.
```text
2021-04-11 19:34:55 +02:00
echo str == "abundante"
" devuelve false
2021-04-11 19:34:55 +02:00
echo str == "abundante desayuno"
" devuelve true
```
2021-04-11 19:34:55 +02:00
Vamos a probar esta otra. Ten en cuenta la letra mayúscula "A":
```text
2021-04-11 19:34:55 +02:00
echo str =~ "Abundante"
" true
```
Devuelve verdadero incluso aunque "Abundante" comience con mayúscula. Interesante... Resulta que mi ajuste de Vim está establecido para ignorar las mayúsculas \(`set ignorecase`\), así que cuando Vim comprueba la igualdad, utiliza mis ajustes de Vim e ignora esa letra en mayúscula. Si inhabilitara esa opción de ignorar mayúsculas \(`set noignorecase`\), la comparación ahora devolvería un falso.
```text
set noignorecase
2021-04-11 19:34:55 +02:00
echo str =~ "Abundante"
" devuelve false porque tiene en cuenta las mayúsculas
set ignorecase
2021-04-11 19:34:55 +02:00
echo str =~ "Abundante"
" devuelve true porque no tiene en cuenta las mayúsculas
```
Si estás escribiendo un complemento para otras personas, esto puede ser una situación engorrosa. ¿Utiliza esa persona `ignorecase` o `noignorecase`? Realmente _no_ quieres forzar a nadie a cambiar sus opciones de ignorar o no las mayúsculas. ¿Qué puedes hacer?
Afortunadamente, Vim tiene un par de operadores que _siempre_ puede ignorar o tener en cuenta las mayúsculas y minúsculas. Para siempre tener en cuenta las mayúsculas, añade un `#` al final.
```text
set ignorecase
2021-04-11 19:43:03 +02:00
echo str =~# "abundante"
" devuelve true
2021-04-11 19:43:03 +02:00
echo str =~# "AbundaNTe"
" devuelve false
set noignorecase
2021-04-11 19:43:03 +02:00
echo str =~# "abundante"
" true
2021-04-11 19:43:03 +02:00
echo str =~# "AbundaNTe"
" false
2021-04-11 19:43:03 +02:00
echo str !~# "AbundaNTe"
" true
```
2021-04-11 19:43:03 +02:00
Para siempre ignorar las mayúsculas y minúsculas al comparar, añade `?`:
```text
set ignorecase
2021-04-11 19:43:03 +02:00
echo str =~? "abundante"
" true
2021-04-11 19:43:03 +02:00
echo str =~? "AbundaNTe"
" true
set noignorecase
2021-04-11 19:43:03 +02:00
echo str =~? "abundante"
" true
2021-04-11 19:43:03 +02:00
echo str =~? "AbundaNTe"
" true
2021-04-11 19:43:03 +02:00
echo str !~? "AbundaNTe"
" false
```
2021-04-11 19:43:03 +02:00
Yo prefiero utilizar `#` para siempre tener en cuenta las mayúsculas y minúsculas y siempre ir sobre seguro.
## If
2021-04-11 20:10:08 +02:00
Ahora que ya has visto las expresiones de igualdad de Vim, vamos a tratar un operador condicional fundamental, la sentencia `if`.
2021-04-11 20:10:08 +02:00
Como mínimo, la sintaxis es:
```text
2021-04-11 20:10:08 +02:00
if {cláusula}
{alguna expresión}
endif
```
2021-04-11 20:10:08 +02:00
Puedes extender el análisis del caso con `elseif` y `else`.
```text
2021-04-11 20:10:08 +02:00
if {predicado1}
{expresión1}
elseif {predicado2}
{expresión2}
elseif {predicado3}
{expresión3}
else
2021-04-11 20:10:08 +02:00
{expresión4}
endif
```
2021-04-11 20:10:08 +02:00
Por ejemplo, el complemento [vim-signify](https://github.com/mhinz/vim-signify) utiliza un método diferente de instalación dependiendo de tus ajustes de Vim. Debajo está la instrucción de instalación copiada desde su `readme`, utilizando la instrucción `if`:
```text
if has('nvim') || has('patch-8.0.902')
Plug 'mhinz/vim-signify'
else
Plug 'mhinz/vim-signify', { 'branch': 'legacy' }
endif
```
2021-04-12 11:45:10 +02:00
## Expresiones ternarias
2021-04-12 11:45:10 +02:00
Vim tiene expresiones ternarias para analizar en una sola línea:
```text
2021-04-12 11:45:10 +02:00
{predicado} ? expresión verdadera : expresión falsa
```
2021-04-12 11:45:10 +02:00
Por ejemplo:
```text
2021-04-12 11:45:10 +02:00
echo 1 ? "Soy verdadero" : "Soy falso"
```
2021-04-12 11:45:10 +02:00
Como 1 es tomado como verdadero, Vim mostrará el mensaje "Soy verdadero". Supongamos que quieres establecer una condición para configurar `background` a oscuro si estás usando Vim después de cierta hora. Añade esto a tu vimrc:
```text
let &background = strftime("%H") < 18 ? "light" : "dark"
```
2021-04-12 11:45:10 +02:00
`&background` es la opción de `'background'` en Vim. `strftime("%H")` devuelve la hora actual. Si todavía no son las 6 PM, utiliza un fondo claro. De lo contrario, utilizará un fondo oscuro.
## Or \(O\)
El "or" lógico \(`||`\) funciona como en la mayoría de lenguajes de programación.
```text
2021-04-12 11:55:59 +02:00
{Expresión falsa} || {Expresión falsa} false
{Expresión falsa} || {Expresión verdadera} true
{Expresión verdadera} || {Expresión falsa} true
{Expresión verdadera} || {Expresión verdadera} true
```
2021-06-15 19:18:09 +02:00
Vim evalúa la expresión y devuelve un 1 \(verdadero\) o 0 \(falso\).
```text
echo 5 || 0
" devuelve 1
echo 5 || 5
" devuelve 1
echo 0 || 0
" devuelve 0
echo "foo5" || "foo5"
" devuelve 0
echo "5foo" || "foo5"
" devuelve 1
```
2021-04-12 11:55:59 +02:00
Dentro del `or` la primera expresión se evalúa y si es verdadera, la expresión siguiente no será evaluada.
```text
2021-04-12 11:55:59 +02:00
let una_docena = 12
2021-04-12 11:55:59 +02:00
echo una_docena || dos_docenas
" devuelve 1
2021-04-12 11:55:59 +02:00
echo dos_docenas || una_docena
" devuelve error
```
2021-06-15 19:18:09 +02:00
Ten en cuenta que `dos_docena` no se ha definido nunca. La expresión `una_docena || dos_docenas` no muestra ningún error porque `una_docena` es evaluada primero y encuentra que es verdadera, por lo que Vim ya no evalúa `dos_docenas`.
## And \(Y\)
El "and" lógico \(`&&`\) es el complemento del "o" lógico.
```text
2021-04-12 12:18:29 +02:00
{Expresión falsa} && {Expresión falsa} false
{Expresión falsa} && {Expresión verdadera} false
{Expresión verdadera} && {Expresión falsa} false
{Expresión verdadera} && {Expresión verdadera} true
```
2021-04-12 12:18:29 +02:00
Por ejemplo:
```text
echo 0 && 0
" devuelve 0
echo 0 && 10
" devuelve 0
```
2021-06-15 19:18:09 +02:00
`&&` evalúa una expresión hasta que ve la primera expresión falsa. Por ejemplo, si tienes `true && true`, evaluará ambas y devolverá `true`. Si tienes `true && false && true`, evaluará el primer `true` y parará en el primer `false`. No evaluará el tercer `true`.
```text
2021-04-12 12:18:29 +02:00
let una_docena = 12
echo una_docena && 10
" devuelve 1
2021-04-12 12:18:29 +02:00
echo una_docena && v:false
" devuelve 0
2021-04-12 12:18:29 +02:00
echo una_docena && dos_docenas
" devuelve error
2021-04-12 12:18:29 +02:00
echo exists("una_docena") && una_docena == 12
" devuelve 1
```
## For
2021-06-15 19:18:09 +02:00
El bucle `for` es comúnmente utilizado con el tipo de datos listas.
```text
2021-04-14 19:05:13 +02:00
let desayunos = ["tortitas", "gofres", "huevos"]
2021-04-14 19:05:13 +02:00
for comida in desayunos
echo comida
endfor
```
2021-04-14 19:05:13 +02:00
También funciona con listas anidadas:
```text
2021-04-14 19:05:13 +02:00
let meals = [["desayuno", "tortitas"], ["almuerzo", "pescado"], ["cena", "pasta"]]
2021-04-14 19:05:13 +02:00
for [tipo_comida, comida] in meals
echo "Estoy tomando " . comida . " para " . tipo_comida
endfor
```
2021-04-14 19:05:13 +02:00
Técnicamente puedes utilizar el bucle `for` con un diccionario utilizando el método `keys()`.
```text
2021-04-14 19:05:13 +02:00
let bebidas = #{desayuno: "leche", almuerzo: "zumo de naranja", cena: "agua"}
for tipo_de_bebida in keys(bebidas)
echo "Estoy bebiendo " . bebidas[tipo_de_bebida] . " para " . tipo_de_bebida
endfor
```
## While
2021-04-14 19:13:05 +02:00
Otro bucle común es el bucle `while`.
```text
2021-04-14 19:13:05 +02:00
let contador = 1
while contador < 5
echo "El valor del contador es: " . contador
let contador += 1
endwhile
```
2021-04-14 19:13:05 +02:00
Otro ejemplo, para obtener el contenido desde la línea actual hasta la última línea:
```text
2021-04-14 19:13:05 +02:00
let linea_actual = line(".")
let ultima_linea = line("$")
2021-04-14 19:13:05 +02:00
while linea_actual <= ultima_linea
echo getline(linea_actual)
let linea_actual += 1
endwhile
```
2021-04-14 19:13:05 +02:00
## Gestión del error
2021-06-15 19:18:09 +02:00
A menudo tu programa no funciona en la manera que esperas. Como resultado, el programa te lleva a un bucle \(valga el juego de palabras\). Lo que necesitas es una gestión del error adecuada.
### Break
2021-04-14 19:20:10 +02:00
Cuando utilizas `break` dentro de un bucle `while` o `for`, esto detiene el bucle.
2021-04-14 19:20:10 +02:00
Veamos un ejemplo, modificando un poco el anterior. Para obtener los textos desde el inicio del archivo hasta la línea actual, pero parar el bucle cuando encuentre la palabra "donut":
```text
2021-04-14 19:20:10 +02:00
let linea = 0
let ultima linea = line("$")
let total_palabras = ""
2021-04-14 19:20:10 +02:00
while linea <= ultima_linea
let linea += 1
let texto_linea = getline(linea)
if texto_linea =~# "donut"
break
endif
2021-04-14 19:20:10 +02:00
echo texto_linea
let total_palabras .= texto_linea . " "
endwhile
2021-04-14 19:20:10 +02:00
echo total_palabras
```
2021-04-14 19:20:10 +02:00
Si tienes el siguiente texto:
```text
2021-04-14 19:20:10 +02:00
uno
dos
tres
donut
2021-04-14 19:20:10 +02:00
cuatro
cinco
```
2021-04-14 19:20:10 +02:00
Al ejecutar el bucle `while` anterior, este mostrará "uno dos tres" y no mostrará el resto de texto, ya que el bucle se detiene por el comando `break` cuando encuentra en esa lista la palabra "donut".
### Continue
2021-04-14 19:29:20 +02:00
El método `continue` es similar a `break`, cuando es invocado en un bucle. La diferencia está en que en vez de detener el bucle, simplemente omite la evaluación actual.
2021-04-14 19:29:20 +02:00
Supongamos que tenemos el mismo texto que antes, pero en vez de `break`, utilizamos `continue`:
```text
2021-04-14 19:29:20 +02:00
let linea = 0
let ultima_linea = line("$")
let total_palabras = ""
2021-04-14 19:29:20 +02:00
while linea <= ultima_linea
let linea += 1
let texto_linea = getline(linea)
if texto_linea =~# "donut"
continue
endif
2021-04-14 19:29:20 +02:00
echo texto_linea
let total_palabras .= linea_texto . " "
endwhile
2021-04-14 19:29:20 +02:00
echo total_palabras
```
2021-04-14 19:29:20 +02:00
Esta vez mostrará `uno dos tres cuatro cinco`. Ahora salta la línea que contiene la palabra "donut", pero la ejecución del bucle continua.
2021-04-14 19:48:32 +02:00
### Try, Finally y Catch
2021-04-14 19:48:32 +02:00
En Vim existe `try`, `finally` y `catch` para la gestión de errores. Para simular un error, puedes utilizar el comando `throw`.
```text
try
echo "Try"
throw "Nope"
endtry
```
2021-04-14 19:48:32 +02:00
Ejecuta esto. Vim mostrará un error `"Exception not caught: Nope`.
2021-04-14 19:48:32 +02:00
Ahora añade un bloque `catch`:
```text
try
echo "Try"
throw "Nope"
catch
2021-04-14 19:48:32 +02:00
echo "Pillado"
endtry
```
2021-04-14 19:48:32 +02:00
Ahora ya no habrá un error. Deberías ver "Try" y se mostrará "Pillado".
2021-04-14 19:48:32 +02:00
Vamos a eliminar `catch` y añadir `finally`:
```text
try
echo "Try"
throw "Nope"
2021-04-14 19:48:32 +02:00
echo "No me verás"
finally
2021-04-14 19:48:32 +02:00
echo "Finalmente"
endtry
```
2021-04-14 19:48:32 +02:00
Ejecuta esto. Ahora Vim muestra el error y el texto "Finalmente".
2021-04-14 19:48:32 +02:00
Vamos a poner todo junto:
```text
try
echo "Try"
throw "Nope"
catch
2021-04-14 19:48:32 +02:00
echo "Pillado"
finally
2021-04-14 19:48:32 +02:00
echo "Finalmente"
endtry
```
2021-04-14 19:48:32 +02:00
Esta vez Vim muestra tanto "Pillado" y "Finalmente". No se muestra el error por que Vim lo ha "pillado". No error is displayed because Vim caught it.
2021-04-14 19:48:32 +02:00
Los errores provienen de diferentes lugares. Otra fuente de error es una llamada a una función que no existe, como `Nada()` que veremos a continuación:
```text
try
echo "Try"
2021-04-14 19:48:32 +02:00
call Nada()
catch
2021-04-14 19:48:32 +02:00
echo "Pillado"
finally
2021-04-14 19:48:32 +02:00
echo "Finalmente"
endtry
```
2021-04-14 19:48:32 +02:00
La diferencia entre `catch` y `finally` es que `finally` siempre se ejecuta, haya error o no. Mientras que `catch` solo se ejecuta cuando tu código tiene algún error.
2021-04-14 19:48:32 +02:00
Puedes detectar un error específico con `:catch`. De acuerdo a `:h :catch`:
```text
2021-04-14 19:48:32 +02:00
catch /^Vim:Interrupt$/. " detecta interrupciones (CTRL-C)
catch /^Vim\\%((\\a\\+)\\)\\=:E/. " detecta todos los errores de Vim
catch /^Vim\\%((\\a\\+)\\)\\=:/. " detecta errores e interrupciones
catch /^Vim(write):/. " detecta todos los errores en :write
catch /^Vim\\%((\\a\\+)\\)\\=:E123:/ " detecta el error E123
catch /my-exception/. " detecta excepciones de usuario
catch /.*/ " detecta todo
catch. " similar a /.*/
```
2021-04-14 19:48:32 +02:00
Dentro de un bloque `try`, una interrupción es considerada un error que se puede detectar.
```text
try
catch /^Vim:Interrupt$/
sleep 100
endtry
```
2021-04-14 19:48:32 +02:00
En tu vimrc, si utilizas un esquema de color personalizado, como [gruvbox](https://github.com/morhetz/gruvbox), y de manera accidental eliminas el esquema de color del directorio, pero todavía tienes la línea `colorscheme gruvbox` en tu vimrc, Vim mostrará un error al ejecutar `source` para volver a tomar en cuenta las modificaciones del vimrc. Para solucionar esto, he añadido lo siguiente a mi vimrc:
```text
try
colorscheme gruvbox
catch
colorscheme default
endtry
```
2021-04-14 19:48:32 +02:00
Ahora al ejecutar `source` sin el directorio `gruvbox`, Vim utilizará `colorscheme default` el esquema de color predeterminado.
2021-04-14 19:51:43 +02:00
## Aprende condicionales de la manera más inteligente
2021-04-14 19:51:43 +02:00
En los capítulos previos, aprendiste sobre los tipos de datos básicos de Vim. En este capítulo, has aprendido cómo combinarlos para escribir programas básicos utilizando condicionales y bucles. Estos están construidos con bloques de programación.
2021-04-14 19:51:43 +02:00
A continuación, vamos a aprender sobre el alcance de las variables.