Vim or Rather Neovim

In the spirit of periodic reconstruction, I have switched to helix.

Notes and thoughts around vim

Setup

To set up python-provider you need to install poetry, create a virtual environment just for neovim, copy the path to init.lua and run :CheckHealth to see if it worked. Make sure that python -V outputs version 3, then:

Quickfix

Generally I use it along with Telescope to send search results to it. Telescope also allows to use the list as input so you can run further searches and filters on it. A possible pain point is that there’s no easy editing of the list. You’d need to set modifiable, change the list, and then cgetbuffer to read the list back into the quickfix list. However, all of this can be mapped to dd just for quickfix buffers.

History

Vim has great history management. To see the code that you had 5 minutes ago, you can do :earlier 5m. It also supports specifying by seconds, minutes, hours, days, or file writes. Its complement is the :later command. The history is stored as a tree so if you undo and then write something new on top of it, the changes you had previously are still accessible in that tree. There are plugins that make traversing this tree easy. It’s the same history that Undotree let’s you see.

You can even do side by side comparisons with something like: earlier 5m | %y | later 5m | diffthis | vnew | put | 1d | diffthis.

The undo history is persistent. You can go back to the file you haven’t opened for months or years and undo it to the beginning.

Settings for editing large files

Creating a minimal.vim config with the following settings and then -u using this config when opening a large file makes editing huge files faster.

syntax off
filetype off
set noundofile
set noswapfile
set noloadplugins

Debugging nvim config

Learned

There are commands that present you with pages and pages of output that you cannot easily deal with since the usual vim commands are not available in the supplied view. For example when running hi. The first approach is to filter down the output with a regex: :filter /Lsp/ hi. However filter does not work with just any output, :h filter shows the commands it works with, but even then the regex applies to only a certain parts of the output. In the case of hi it only works on the highlight groups. A more optimal way is to :redir the output to a file. For example :redir > /tmp/hi, then run :hi, scroll to the bottom of the output, :redir END and :e /tmp/hi. redir would fail if you try to redirect output from a command that itself calls redir internally.

Vim Search And Replace

The / register stores the last search.

There is a simple regex based search and replace:

If you do a find and replace on a line, then go to another line and want to do the same thing there, then & will do just that. It executes the last search and replace. If you, however, want to do it for all of the lines, then g& does it for all. Using S will replace by preserving the case.

There is a nice command called gn for searching for the last used pattern and starting a visually selecting the match. We can use it along with c, to change the next visual match which would make replacing repeatable with the . command. Our find and replace flow would now be something like:

Another powerful feature of Vim is the :normal command that allows running normal mode commands together with some selection. E.g. you have a visual selection, or % that uses the whole buffer: :% norm wvwU which would select the 2nd word on every line and uppercase it.

Then there is the global command that matches lines based on a regex and then allows running commands against these matches. For example we have a python file that is missing colons on all of its if statements, then :g/if/norm $a: would fix that for us. We can also run macros over the matches with normal, :global/foo/norm @w. Something to note here is that the signature is g/{pattern}/{cmd}, so normal followed by whatever is the command, but some normal mode commands are also exposed as command line commands. g can be prefixed with a range, as by default it looks for matches in the whole document.

Vim searches can also use regexes. Depending on the magic level, they either need to be escaped, some need to be escaped, or the can be used freely. A good rule here is to use \v for very magic, when you need to use a regex without having to escape them or \V for very no magic, for a no regex matching, i.e. everything is matched literally.

g& can be used to run the last substitute command, but on the whole file instead. This is for the cases when you substitute something, but realize that you should have used the % in front of the s.

There is a neat trick with :g/MATCH/#|s/MATCH/REPLACE/g|# when we want to still somewhat confirm that what we substituted was what we wanted to substitute. This command would print the line before and after, so you can visually confirm the substitution.

The vim-abolish plugin can do some neat swaps. :%S/{man,dog}/{dog,man}/g would swap the two words without having to create a dictionary that we could reference in the middle of our substitution - the vanilla vim way of achieving something like this.

Vim Visual Mode

Vim has three different visual modes: a character-wise, a line-wise, and a block-wise, that are entered with v, V, and <C-v> respectively.

<C-g> in visual mode enters select mode, any key then pressed will trigger insert mode and replace whatever was selected. Not 100% sure why something like this is necessary, c seems to do the same.

If we are in some sort of visual mode, then the visual modes’ keys also switch between these modes. E.g. we are in block-wise mode, but then realize we want to do a visual line mode instead, then V will switch to that without having to repeat the motions - applying to the already selected lines.

If we were to repeat a visual mode command with ., then it acts on the same “range”, e.g. if it was invoked on a word that was 3 characters long, it will always work on only 3 characters. It is better to prefer normal mode commands. For example uppercasing with veU and then j. twice. gUe would work better in this case.

ONE
TWO
THRee

An example where we append something to every selected line:


A block selection for editing line beginnings and ends. Wrap all of the following lines in - "<line>".

Tried to escape just yesterday
Though lately I can't pull myself away
We see ourselves above the fray
But still the sequence must be entertained

The full sequence would be: <C-v>3jI- "<esc>gv$A", select all line beginnings insert the beginning, select the last selection, $ goes to line ends, append the double quote.

Vim Macros

Vim macros are sequences of vim commands that are recorded by the user, are stored in registers, and can be played back at a later time.

For example if we have the following text and we want to change each . into a ), and capitalize the word.

1. one
2. two
3. three
4. four
5. five

We start with our cursor on 1, and execute the following sequence of keystrokes: qq0f.r)w~jq. Motions such as w are more robust than moving cursor a certain number of positions. We use f, because we want to find the first . in whatever position. It also serves as a good safety mechanism, because the macro exits if it cannot find a . on the line. If we execute our macros in series with something like 5@q, and then on the third line down we had a comment line, then the whole execution stops there and any line below won’t be changed. An alternative approach is to run macros in “parallel”, e.g. we specify some range of lines and run normal @q on that range. In such a case our macro doesn’t need the j for moving down a line, but it won’t break even if we have it there.

I also have a custom function that allows running a macro over selected lines. In this case I can simply do something like vip@q to run the macro over a paragraph.

It is possible to save commonly used macros under a key map. For example, If you save a macro to the w register, then "wp will paste the stored macro to the file. This can then be stored in a variable as a string and attached to a key map.

A mapped macro that replaces all require statements with imports in JS files in an instant.

let @i = 'ceimport^[f=cf(from ^[f)x'
nnoremap <Leader>ri :g/require/normal @i<CR>

It may be beneficial to keybind commonly used macros. If the macro is ran on single cases, such as words or lines, then the action can be directly put behind a keymap. On the other hand, if it is used in wider cases, such as applying the macro to a bunch of lines, it could be better to write an user command that loads the macro’s actions into a given register.

Editing macros

Since macros are stored in registers, they can also be edited. For example if we have recorded a macro in register w then:

Alternatively, the same can be achieved just on the command line which is a bit cleaner approach.

When we specify a register as a capital letter, whatever we want to put there then, will be appended to what is already there. If we record a macro and realize that we should have ended it with a j motion, then we can do a simple qAjq to append to our recorded macro in register a.

Usually we don’t think about it this way, but . or the dot command can be considered a macro. At least to some extent. If you were to enter the insert mode then every keypress is recorded which can be played back with the dot command.

The dot command is a useful tool, but some of its actions cannot be repeated with a numeric count, e.g. cgn after which we want 5.. A nice workaround with macros for this is to wrap the . in a macro qq.q after which we can do 5@q to repeat our dot operator five times.

Macros can be run over a lists, such as argument list, buffer list, window list, or tab list. If we have a macro recorded in register a, then we can do a simple :argdo normal @a This runs the macro in “parallel”. In series approach would end the macro by navigating to the next file. :wa would then save all the modified buffers, or alternatively when the autowrite setting is on, the buffer is saved when navigating away from it.

An approach to inserting numbered indices to a list with a macro and the expression register.

Cream-colored ponies and crisp apple strudels
Snowflakes that stay on my nose and eyelashes
Silver white winters that melt into spring

We start in the beginning of the first line, but before we create a variable and assign number 1 to it: :let i = 1, then qa<C-r>=i<CR>) <Esc>:let i+=1<CR>q

1) Cream-colored ponies and crisp apple strudels
2) Snowflakes that stay on my nose and eyelashes
3) Silver white winters that melt into spring

Vim Registers

There are 10 register types in Vim:

If we want to do something with a register, in normal mode, we would type " followed by a register name and then what we want to do with it, p to paste its contents. In insert mode it is <C-r> to select a register which then pastes its contents.

Actions carried out by dcsxy keys and their capitalized alternatives, DCSXY, will fill the unnamed register. The text that you yank will occupy the 0 register and the unnamed one - ". If you paste that text over something, then whatever was underneath will occupy the unnamed register next. By default paste uses the unnamed one, so if you want to paste another time, you won’t get what you initially yanked. To get around this use 0p to use that register instead. Each time we delete or change something it goes into the 1 register and pushes whatever was there before, to the next numbered register and if 2 was also filled, then it gets shifted to 3 and so on. Whatever is in 9 gets lost. There are exceptions to this as not every delete or change operation will populate a register (:h quote_number).

Any delete that is less than one line will be put in the -, or the small delete register.

We can specify the register that we want to use for our operation, by prefixing the command with the register’s name. "adw would delete the word and move it to register a. Similarly with Ex commands: :delete c would delete to register c. Specifying an uppercase named register appends to it, instead of overwriting.

We have 26 named registers that can be used for anything really. Macros are also stored in them.

The three read only registers :, . and % store the last ex command, the last inserted text and the current file, respectively. I should really use : more often, since sometimes writing lua scripts I find myself exploring some command on the command line and then when I manage to make it work I struggle to write it into the buffer. The . gets used very often. % can be used when you, create a buffer in a directory that doesn’t exist and then go to save it. It throws, since the directory nor the file is there. :!mkdir %:h makes the directory and we can write our file with :w now.

# holds the alternate file name.

The expression register :h @= allows for evaluating expressions. In insert mode pressing C-r= allows for writing mathematical expressions that then get evaluated or <C-r>=system('ls') to get every file name in the current directory written to the buffer.

_ is called the black hole register. When we write to this register it gets lost. For example if we delete something that we don’t want to show up in our registers we can do "_dd. We can also clear registers this way: :let @2=@_ would clear register 2.

+ and * are for clipboard and how they behave is system dependent. Setting set clipboard+=unnamedplus just makes everything work.

/ stores the last search which can be used to correct a search, insert last search when you’re typing, or searching for something, finding it, doing a :%s/ where we can a quickly insert the searched word with <C-r>/.

Vim With Git

vim-rhubarb extension enables some nifty features from GitHub. I.e. while writing a commit message, <C-x><C-o> allows for referencing issues and pull requests.

Conflict workflow