DEV Community

Cover image for Becoming a wizard with Vim
Sérgio Araújo
Sérgio Araújo

Posted on • Updated on

Becoming a wizard with Vim

The problem:

I have a block in a yaml file that I would like to yank and paste several times:

- probeNumber: 1
  probeLocation: [0, 0, 0.1]
Enter fullscreen mode Exit fullscreen mode

Is it possible to automatically increment the value of probeNumber and probeLocation in each block? Like so:

- probeNumber: 1
  probeLocation: [0, 0, 0.1]

- probeNumber: 2
  probeLocation: [0, 0, 0.15]

- probeNumber: 3
  probeLocation: [0, 0, 0.20]
Enter fullscreen mode Exit fullscreen mode

probeLocation is not an integer, it is incremented by a fixed value in each block (0.05). I need 1000 or so of these blocks so doing this manually is not feasible.

fast and magic solution (creating the file):

Just select the 3 first lines (we need at least one blank line between each block), copy to the clipboard and run:

:for i in range(999) | silent! 0put + | endfor
Enter fullscreen mode Exit fullscreen mode

The second problem

Now increasing the numbers to fit your needs

In order to increase probeNumber lines:

:let c=1 | g/\d\+$/ s//\=c/ | let c+=1
Enter fullscreen mode Exit fullscreen mode

Now the lines with probeLocation:

:let c=0.10 | g/0\.1\ze]/ s//\=printf("%0.2f", c)/ | let c+=0.05
Enter fullscreen mode Exit fullscreen mode

In the above global commands we are using a counter c=1 and increasing it. At the substitution part we are usig an expression register \=something. On the printf function we are converting the counter to a float point number with two digits. On each line of the global pattern we are also increasing the counter by 0.05.

Substitute spaces for dashes but not on entire line:

Supose you have these lines and want to remove spaces only i the name of the files

cp 01-file with spaces.txt $HOME/tmp/01-file with spaces.txt
cp 02-file with spaces.txt $HOME/tmp/02-file with spaces.txt
Enter fullscreen mode Exit fullscreen mode

One regex to match only the filenames could be:

/\d\+-[^.]*
Enter fullscreen mode Exit fullscreen mode

The solution the will be:

%s/\d\+-[^.]*/\=substitute(submatch(0), ' ', '-', 'g')/g
Enter fullscreen mode Exit fullscreen mode

Or if you have something like:

Time in hours, minutes and seconds which I want to substrat 00:04:01
Time in hours, minutes and seconds which I want to substrat 00:05:01
Time in hours, minutes and seconds which I want to substrat 00:06:12
Time in hours, minutes and seconds which I want to substrat 00:14:01
Time in hours, minutes and seconds which I want to substrat 00:00:01
Enter fullscreen mode Exit fullscreen mode

And you want to decrease 10 seconds in every match:

:%s/00.*\ze$/\=system('date -u -d "'.submatch(0).' +0000 -10 sec" +"%H:%M:%S"')/g
Enter fullscreen mode Exit fullscreen mode

I have created a script called less2sec that uses the date command above and have a default second decrease '02' and a default hour, just to give the user an example. Take a look on the script:

#!/bin/env bash
# Bash script to decrease [defaut 02] sec
# Last Change: Tue, 31 May 2022 15:52
# vim: ft=sh

# Note: I had to remove the clear command because it
# puts some trash on my final output I use on vim
# https://unix.stackexchange.com/a/564986/3157

HELP(){
        cat <<-EOF
                  HELP
        -------------------------------
        Subtract seconds from HH:MM:SS"
        -------------------------------
        ${0##*/} [-h] [hora] [segundos]

EOF
}

[ "$1" == "-h" ] && HELP && exit 1

[ "$1" ] && hora="$1"  || hora="11:17:00"
[ "$2" ] && param="$2" || param="02"

# echo -e "\t${0##*/} Script para subtrair segundos"
# echo -e "\tImprimindo $hora menos $param segundos:"
hora=$(date  -u -d"$hora +0000 -$param sec" '+%H:%M:%S')

# here we are using "printf" to avoid  the new line character
printf "%s" "$hora"
Enter fullscreen mode Exit fullscreen mode

We have to add a space after the script name and befor the amount of seconds we want to decrease.

:%s/.*/\=system('less2sec ' . submatch(0) . ' 10')
Enter fullscreen mode Exit fullscreen mode

The only catch is that your regex, the search part, must match a column of text like this: 00:01:00.

Give me some feedback, some insight to improve this article or correct any possible mistake.

Top comments (3)

Collapse
 
filbranden profile image
Filipe Brandenburger

To create the file with 1000 copies of that block, just simply 999P from the first line is enough, no? No need for a long for loop there...

The stuff with incrementing a Vimscript variable from a :g and using it in a :s for the second part is genius! Very nice solution.

Great article!

Collapse
 
sirseanofloxley profile image
Sean Allin Newell

I mean... If you really need something like this wouldn't helm, go templating, or another higher level yaml+k8s abstraction work?

BUT.
If we must have a big ol hand cranked yaml file, then yeah, this is the shit.

Collapse
 
voyeg3r profile image
Sérgio Araújo

I have used a stackoverflow question just to show how powerful vim is, how fast you can make complex changes in files.