loading...

Vim: Math on vim substitution

voyeg3r profile image Sérgio Luiz Araújo Silva Updated on ・3 min read

Target content

I stumbled upon and downloaded an interesting python tutorial on youtube and it brings on its annotations something like:

1. Variables (2:47)
2. Functions (8:50)
3. Calendar (14:10)
4. If Else Statements (37:17)

I have created a README.md file with this annotations, but what happens is that the youtube video link itself uses a sort of sum-up of the time in seconds, which means that 2 minutes and 47 seconds are in reality 167 seconds and I needed to convert it to create links to its parts.

Using vim skills let's see how to convert the time properly.

Introducing our vim friend submatch()

When using Regular expressions we can use groups like this

/\v^(word) (another)

The above search matches "word another" using "very magic" option and also create two regex groups. Now we can reuse the last search like this to exchange words position:

:%s//\2 \1

\2 matches the second group and \1 matches the first one. Now we can use our friend submatch(), which uses these groups to perform actions like mathematics.

The only problem is that we already have parenthesis in our file, we just need to protect them this way:

/\v\((pattern)\)

The \( in very magick means a real parenthesis and a non scaped one ( means start of a regex group.

Actually, if you don't use very magic search you kind of have to invert the use of parentheses. With very magic regex groups are not protected and normal parentheses are if we do not use very magic the logic is the oposite.

The substitution command

The below substitution only substitues minutes+seconds to seconds, it does not creates markdown links as I wanted

    :'<,'>s/\v\((\d+):(\d+)\)$/\=submatch(1)*60 + submatch(2)

We used a selection to group the lines that have only "minutes + seconds", that's why '<,'>.

\v ............... very magic enabled
\( ............... literal parenthesis
( ................ start of the first regex group
\d+ .............. at least one digit
: ................ literal colon
\) ............... closing literal parenthesis
$ ................ end of line
\= ............... expression register
submatch(1) ...... the first regex group

Building the final link (markdown)

The video link is https://www.youtube.com/watch?v=4F2m91eKmts and I have saved it on the a register plus &ts= to indicate the video part in seconds:

:let @a='https://www.youtube.com/watch?v=4F2m91eKmts&ts='

the search pattern

/\v\. ([^(]*)\((\d+):(\d+)\)

\v ....................... very magic search
\. ....................... literal dot
([^(]*) .................. a regex group that matches everything but opening parenthesis
\( ....................... a literal parenthesis
(\d+) ..................... a digit or more as a regex group
: ......................... literal colon

To be able to creat a markdown link we have to group our submatch() so then we can combine submatches with the strings
we are going to use in our links. The markdown links are like: [link name](url).

:'<,'>s//\=(' ['. submatch(1) . '('. (submatch(2)*60 + submatch(3)). ']')
:'<,'>s//\=(' ['. submatch(1) . '('. (submatch(2)*60 + submatch(3)). ')](' . @a . ')')

'<,'> ................... selected area
// ...................... uses last search
\=( ..................... here we open our big grouping pattern

The part were we combine minutes plus seconds is between parenthesis

How old they are

Let's say you have these lines:

Stallman   Richard   GNU    1953  USA
Wall       Larry     Perl   1954  USA
Moolenar   Bram      Vim    1961  Netherlands
Tridgell   Andrew    Samba  1967  Australia
Matsumoto  Yukihiro  Ruby   1965  Japan
Ritchie    Dennis    C      1941  USA
Thompson   Ken       Unix   1943  USA

And you want to know how old each one of these guys is right now

:%s,\d\+,\=strftime("%Y")-submatch(0)
:'<,'>s/\v(\d+)/\=("Age: ".(strftime("%Y")-submatch(1)))/

In the second example we had to group all into parentesis \=() so then we can conatenate the string with the number

Notice: If you want to combine "strings" with numbers you can group the math parth and use "." to concatenate them.

Just increasing and decreasing numbers

Let's say you have these lines:

On this line I have 1 and nothing more
On this line I have 1 and nothing more
On this line I have 1 and nothing more
On this line I have 1 and nothing more
On this line I have 1 and nothing more
On this line I have 1 and nothing more

And you want change them to this:

On this line I have 1 and nothing more
On this line I have 2 and nothing more
On this line I have 3 and nothing more
On this line I have 4 and nothing more
On this line I have 5 and nothing more
On this line I have 6 and nothing more

The simple solution is:

Jump to the first 1 on on the second line:

jf1

Start visual block selection with Ctrl-v and go down 4 lines
with:

4j

Now just press:

g Ctrl-a

Posted on by:

voyeg3r profile

Sérgio Luiz Araújo Silva

@voyeg3r

I am a Free Software enthusiast and a (neo)?vim addicted, I also like shell script, sed, awk, and as you can see I love Regular Expressions.

Discussion

markdown guide
 

Great tips overall! Very nice article. It has most of my favorite math tricks in Vim.

One important caveat to watch for is that Vim will interpret numbers like 047 as octal (so that's actually 39 rather than 47.) In your particular examples, that wouldn't matter, because you only have two digit seconds there, and 01 through 07 will behave the same whether octal or decimal, and Vim will interpret 08 and 09 as decimal. So it all looks good... But when you start having three-digit numbers with leading zeroes, that's something to watch for...

I love the g Ctrl-A trick in block visual mode! (Or, more accurately, I just love block visual mode! So cool!)

Thanks for the article again. Looking forward to reading more Vim articles from you.

 

Hi, Filipe!

I have found a great article I backed up some time ago about "how amazing vim regexes are" and I think you will like reading it:
bitbucket.org/snippets/sergio/7nzqxx

I suggest increasing the font size for better reading.