🪴\zansh.in\blog

39 min read / 11 for this part

VIM diary

Part: 1 | 2 | 3 | 4 | 5 | 6 | 7

While learning the basics of Vim over the course of a few months I kept a simple diary. I recorded what I had been Googling for and what I'd learned during my struggles.

# December 2023

I thought this would be the "settling in" month.

Maybe it was, towards the end, in a tantalising "just a little further" sort-of way. But everything leading up to that was tinged with the regret of having started down this god-forsaken path in the first place.

As I became more familiar with Vim and gradually customised it to my liking, a peculiar thing happened: Because I still needed to swap between it and VSCode I noticed myself hating both of them more and more.

This irritation didn't seem to be passing very quickly. When one hurdle was overcome in Vim, it fed into my hatred of VSCode. When I used a snazzy feature in VSCode that I have keen muscle-memory for, it fed into my hatred of Vim needing a whole bunch of jostling to get things done.

Somehow I was still enjoying working on the note-taking project I had assigned myself (and taking actual notes with) but when it came to writing code in a large project things were pretty painful.

# Neovim again

Back in October I had tried LazyVim and decided I didn't want to learn Vim that way. However, I certainly did remember that it was very fast compared to regular Vim, at least on the hardware I was using at work.

I tried LazyVim once more. Within minutes I realised I hate tabs, animations, the peculiar futzing with standard functionality (why is the Ex prompt in the middle of the screen?) and a whole bunch of things I didn't want to go through the effort of turning off.

So I deleted the LazyVim config to see if I could instead use my own home-grown .vimrc that I was starting to become attached to.

Perhaps I was starting to get the point a bit?

It took two attempts over the first week, but I got my hand-rolled .vimrc working in both Vim and Neovim. This was not without problems of course: I rather like Fuzzyy, but it is written in VimScript9 and thus incompatible with Neovim. But fzf didn't work properly for me at the office in neither Vim nor Neovim, where I used GitBash on a Windows machine.

Some conditional logic sorted that out and I was off and running with the much snappier Neovim. It looked and worked almost exactly like Vim. I would, however, fall-back to using Vim again in Windows due to GitBash and fzf bugs that I just couldn't figure out. In the New Year I'd finally give up on using fzf on Windows (just in Neovim, not the CLI) and use Telescope in order to get me back into using Neovim regularly.

# Taking stock

To get a handle on some of these and other issues, I spent some time in the first week reviewing all the plugins I had installed. I enumerated them one-by-one and tried to justify each. I dropped Copilot, vim-abolish, vim-repeat, vim-surround, and nearly all colorschemes.

This is not a judgement about any of those plugins by the way: I just feel too green at regular Vim to be doing too much in the way of adding new motions that aren't portable.

# The UNIX Way

Into the second week I was looking into brushing-up on shell commands that work well with Vim. This was off the back of trying to use Vim pipes more.

My list looked like this:

head | tail -n 10 - crop output

expand - convert tabs to spaces

fold -s -w 50 - hard line wrap (80 is default, -s to wrap on spaces)

cut - cut lines by:


nl - print with line-numbers. -ba to number empty lines

wc - word count. -w words only, -l lines only

column -t - detect and print in columns

Before | After
---------------|----------------
one = 1 | one 1
two = 2 | two 2
three = 3 | three 3
four = 4 | four 4
five = 5 | five 5
six = 6 | six 6
seven = 7 | seven 7
eight = 9 | eight 9

The above four can accept [:class:] thingers in their regex substitutions. Here's a shortlist of ones I find useful:

[:alnum:] [:alpha:] [:digit:] [:lower:] [:upper:] [:punct:] [:space:]

find - find files + folders, and can execute commands with the results using --exec or piping into xargs


diff -u old new > file - create a patch file

patch < file - apply patch file

patch -R < file - [r]everse/undo patch


date - insert date/time

cal - insert a nice calendar


bc - arbitrary precision calculator. I eventually wrote something that integrates this

printf - more than echo

xargs - build commands from stdin / pipelines. It's basic function is to take a list of newline-separated strings (like you'd get from an output) and turn them into space-separated strings that you can pass as an input argument. For example:

So long as your files don't have spaces, the above will work without any special arguments passed into find/xargs (such as -print0 and --null.) I find the curl example interesting because piping a URL into it isn't possible with the standard command.


My starting point for looking into these was this Wikipedia article about POSIX commands. I tried to focus on the "Mantadory" ones where I could.

It was a nice exercise in discovering commands I had not heard of, plus refreshing what little knowledge I realised I knew.

I would later recognise this as the point where I started to finally grok the value of the UNIX philosophy: Where CLI commands do one job well and are interoperable. And since so many are for manipulating text, you can use them in any flavour of Vim, even the much older and more limited Vi.

Better than that, by using them in Vim instead of reaching for plugins, you're keeping your memory fresh for when you want to use them at the CLI or in a shell script.

# UNIX Philosophy

Half-way into December I stumbled upon a few ranty videos about the UNIX Philosophy. It was a serendipidous moment considering what I had been looking into earlier in the month:

While this was clawing around in my head trying to grab a finger-hold, I came up with another project: a "filter" shell-script that works (as much as it can) like the Soulver app.

It implements bc alongside various filters and token replacements to do so, for example:

1 + 1

Becomes:

1 + 1 #= 2

It will ignore things that aren't arithmetic operators or numbers:

50 litres * 100 bags #= 5000
29.99 total price #= 29.99

You can use LINE:-n and LINE:+n to refer to the result of a line relative to the one they appear on:

50 litres * 100 bags #= 5000
29.99 total price #= 29.99
price per litre = LINE:-1 / LINE:-2 #= .005998

Running the "filter" on the same lines will recalculate them. I eventually called it solv-sh and popped it on GitHub.

# Pain switch

By the last week before Christmas I finally starting to find VSCode slightly more painful to live with than Vim/Neovim.

Also, Neovim in Windows isn't quite up to scatch with Vim when it comes to a bug-free experience running external shell commands, so I'm finding myself using Vim more even though Neovim is far more performant.

Looking back, I would realise this was the beginning of a long drawn-out detachment from VSCodes built-in source control tools. I had discovered the git worktree command, and while I had not played with it at this point very much, it was going to become my standard way of interacting with repos, and I would be doing so mostly via the CLI and vim-fugitive.

Until then, I was scratching my head wondering why worktrees don't seem to play well with VSCode's Source Control feature. I would later realise this was down to using bare repos, which I mistakenly thought was a requirement for using git worktree.

It's not.

# Forced Hand

As stated previously, I use a Windows machine at work. It had problems, and I was due a replacement. However, there was a mix-up with permissions and I could no longer run any developer tools on either my old or new laptop.

Except Vim. So that's what I used exclusively from that point on.

I wasn't planning on doing so: I was in the midst of a rather major project that could really do with as few distractions as possible, least of all while I was still trying to get used to a new way of working. The only thing I was doing with Vim with any regularity was taking notes, which isn't quite the same as conjuring the many special characters required when programming. There was still a lot to get used to.

But since IT support weren't exactly quick on the uptake it felt like this was an opportunity to go all-in on this "side-project" of learning a brand new development environment. Well, no, it didn't really feel like an opportunity at all. I wasn't ready, but the project had to continue.

I did learn a couple of neat tricks as a late Xmas present:

First a convenient search/replace technique discovered on Reddit:

onoremap <expr> * v:count ? '*' : '<esc>*g``' . v:operator . 'gn'

With this remap you can use c* to change (or d* to delete, etc.) the current word. Then after hitting <esc>, either:

Second, how to list files recursively excluding node_modules:

find . -path ./node_modules -prune -o -name '*.js' | xargs grep -l '^import'
# | \____/ \/
# | | OR
# | |
# | |_ If a directory, do not descend into it
# |
# |____ the ./ is important, and don't add a trailing / either

Clever! We ask find to look for a path first, but if it finds a path, not to descend into it. Then we OR that with a search for *.js files, which will be returned to us instead of the pruned node_modules folder.

Perhaps you have heard of ripgrep? It achieves the above in a single command. It's nice to know a portable alternative though.

That rounds out 2023!

# Commands Learned

Next part →

31 Dec, 2023

Google it:

🌸 git worktree 🌺 GitBash 🌷 The UNIX Philosophy 🌻 VimScript9