39 min read / 7 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.
# November 2023
A wild month of Vim digestion theatre. Towards the end of it I would make my
first changes to a large project at work, including using vim-fugitive to
push them up.
But it didn't feel like I would get there from here.
The first week of November was spent looking into things like LSPs, linters and
formatters, Git integration, discovering fzf, and also taking the time - 2
months in - to run through Vimtutor from start to finish. I looked for ways
of breaking out of INSERT mode without using ESC or remapping it to the
CAPS-LOCK key. I was trying to keep as "vanilla" a mindset as I could bear.
# Refactoring
I hit my first major refactoring frustration that I consider very straightforward in VSCode, which is...
// ...turning this:
api.first()
api.second()
api.third()
// ...into this:
const { first, second, third } = api
first()
second()
third()
Trying to solve this without multiple cursors
took a fair bit of thinking about. The first working and memorable solution I
came up with used markers and a macro, and worked something like this:
// Step 1: Insert an empty const at the top
const {} = api; // Step 2: Place a marker here. I used `a` for "anchor"
With this preamble in place, I recorded a macro to search for api., delete
it, copy the word that had followed it, then paste it above the a marker
using P. I'd then repeat the macro to complete the task.
I discovered I could also cast this magic spell:
:let @a="" | g/api\.\w\+/ exec "norm! 2w\"AyiwBd2w" | let @a=@a.', '
But appending to registers with let isn't supported in the VSCode Vim
Extension, and VSCode was still my main workhorse at the office.
# Registers and Macros
Around this time I discovered that the registers used for yanking (copying)
and putting (pasting) are the same registers used to record macros to. This
explains why you can paste a macro into a page, and why you can run yanked-text
as a macro.
In other words, the interface for editing a macro is your current buffer.
Not that I arrived that this realisation happily. Despite progress it did feel like I was reaching a ceiling with how far Vim could be depended-on to maintain a large project. VSCode has sophisticated refactoring tools built-in, nevermind what can be done with extensions.
Later I would discover that the combination of markers, text-objects, and
relative movement that Vim offers is decent for tackling a range of tricker
refactoring jobs. More up-front thought is involved, but execution is quick, so
the more files you have deal with the more it pays off.
I was also yet to realise just how powerful CLI tools can be for refactoring, and how easily those can be integrated into Vim without the need for plugins.
Into the second week more revelations about the grammar of Vim was trickling
in. Not only could I format text = after selecting it, but I could instruct
format = to operate on a text-object. The most difficult grammatical concept
for me (so far) is understanding repeated steps: For example, opening o
multiple blank lines at once, or incrementing CTRL-A a range of numbers all
at once within a selection:
Before | After
---------------|----------------
one = 0 | one = 1
two = 0 | two = 2
three = 0 | three = 3
four = 0 | four = 4
five = 0 | five = 5
six = 0 | six = 6
seven = 0 | seven = 7
eight = 0 | eight = 9
I was tripping over myself often enough that I put together a plan of deliberate practice focused on only a few Vim commands (which are actually Ex commands) or movements at a time.
# Project
At the start of the third week I discovered a project I could use as a vehicle for practicing Vim at work. I was glad to have the excuse, as I was noticing ever more bugs and missing features while using the VSCode + Vim Extension. I couldn't move away completely (yet?) but since I write my notes using Markdown I could use vanilla Vim for that.
Denote is a file-format and note-taking plugin for Emacs written by Protesilaos Stavrou. I learned about it from a Reddit user who wrote themselves a simple plugin that offered some of Denote's basic features in Vim.
Seeing how it could help me maintain my own notes, I decided to write a shell-script version with the intention of also creating a companion VimScript plugin to integrate with it. I would focus only on the Markdown with YAML front-matter version of Denote, since that's essentially what I was trying to do with my own system.
# denote-md's help output (once I'd written it)
Usage: denote <command> [<args>]
new [tags] [title]
get filename [identifier]
get title note.md
replace title [new_title] *.md
list tags [*.md]
list notes [tags]
list recent [num_notes]
list backlinks note.md
list heading [heading] [tags]
list actions [tags]
add tag [tag] *.md
remove tag [tag] *.md
rename tag [tag] [new_name] *.md
replace tags [new_tags] *.md
refresh *.md
env Print environment variables
At the same time I also threw together a JavaScript to rewrite the file-names and front-matter of my existing notes to the Denote format.
These little projects were great for practicing what I was learning and discovering new ways of doing things.
# Trees: Both Undo and File ones
Despite struggling to really "click" with Vim, at this point it was getting hard to imagine life without undo-tree.
It's a plugin that puts a UI around Vim's amazing built-in undo system. If you
adjust your .vimrc slightly to boost your undo-history and persist it to
disk, nothing you type is lost. It feels like you have automatic local version
control.
Speaking of trees, I notice that I'm avoiding installing anything to replace
Netrw, such as NERDTree. I believe if
I get used to buffer management, splits, the standard :find * command, and
fuzzy-finders, and even just dipping in-and-out of the shell, I'll lean on
file-trees a little less.
Yes, file-trees really are great for casually browsing a project, and there's something cathartic about opening and closing those little folders.
But what if I miss out on better ways of doing things by falling back into old
habits? I had yet to realise I could :cd :pwd and :r!find ... (that's the
POSIX find, not the Vim command mentioned two paragraphs up) to paste all or
part of my entire project into a buffer and just gf and <C-i> / <C-o>
around it to my hearts content.
All that, plus the frustration that led up to such revelations, was still ahead of me.
# Commands Learned
- Netrw:
popens preview window,<C-w> zcloses it - Netrw:
mtset target directory formmmove andmccopy operators (aftermfmarking files/folders first) - Jump to matching {([ or ])} with
% - Read a file into the current buffer with
:r filename wis not just for the whole buffer: it's a command like any other, and you can apply it to VISUAL mode to write-out the current selection<C-w> whandy if you only have a single splitcat the end of a substitution:%s/././cprompts for confirmation- Code folding:
zffoldzdunfoldzMminimise allzRrestore allzEerase all [Ilist all lines containing the word under the cursor- Appending to a register by using its uppercase name,
avs.A :copy/:tagain. Feel like I tried learning this one already?7o<Esc>open 7 blank lines:nohremove highlight of last search- Delete more than just lines
dapdipetc - Learned about
\%V, but have not use it yet gqlapply hard-wrap to the current line- Doing
cip""Pa lot, but stubbornly refusing to usevim-surround :g/norm!...and:g!/norm!are great- Diff view:
]cnext change,[cprevious,doobtain,dpput :registersis a thing- I remapped
Jin VISUAL mode, but can still use its previous behaviour with:j
30 Nov, 2023