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:
poetry new NeoVimVirtual
cd NeoVimVirtual
poetry install
poetry shell
poetry add pynvim
poetry env list --full-path
- The activated path with/bin/python
is the path that should be in theinit.lua
.
Quickfix ¶
:cdo[!] {cmd}
- Execute{cmd}
in each valid entry in the quickfix list.:cfdo[!] {cmd}
- Execute{cmd}
in each file in the quickfix list.:ld[o][!] {cmd}
- Execute{cmd}
in each valid entry in the location list for the current window.:lfdo[!] {cmd}
- Execute{cmd}
in each file in the location list for the current window.
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
.
- jump to earlier
- yank
- jump back to current
- diffthis
- new vertical split
- paste
- delete the first empty line
- diff with this
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 ¶
nvim -u min.vim
use a minimal config to see if an issue still occurs- packer installed plugins seem to be still sourced even if packer is commented out
nvim -u min --clean
seems to get around this
- Does an issue only happen in a git repo or not or both
Learned ¶
- Vim sources its own python.vim after mine in the ftplugin directory, so if you want your own configurations to be set, you need to put them in after/ftplugin. Check with
verbose
to see where something is set. g;
- jumps to the location of the last editq:
- in normal mode opens command search window, or<C-f>
in command mode. I have removed the<C-f>
mapping as I was pressing it accidentally ever since I mapped<C-p>
to an up arrow.q/
orq?
does the same for searches
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 has a built in support for rot-13, with
g?
on a visual selection. - there is
nrformats
which specifies what will be considered for incrementing when pressing<C-a>
or<C-x>
. For Neovim it is binary and hex, but it can also work on singular letters or octal values. :help i_META
meta key along with something else acts as if<Esc>
had been pressed. For example<M-o>
in insert mode, inserts a new line below without exiting it - i.e. it is the same as<Esc>o
ga
- revels the character code for what’s under the cursor. The revealed hex code is what we would need if we wanted to insert the character with<C-v>
.:as
is the same command on the command line.gR
converts tabs to spaces in replace mode, so the line won’t be shortened drastically when a wide tab is replaced by a single width character.:ilist <keyword>
show the results with line numbers next to them. The command may need a!
after it, as sometimes it cannot find anything:h v_g_ctrl-a
if you highlight a visual range of numbers, then this increments every consecutive line by 1 more than the previous line. E.g. haveline1
50 times,<c-v>
over the numbers,g<c-a>
, and the numbers will range from 1 to 50. This is better than doing a macro that yanks a line, pastes and increments the number by 1.<C-r><C-w>
inserts the word under the cursor into the command line, while on command line. I actually use this for mysed
mappings.<C-r><C-a>
gets the WORD,- you can use
read
to get output from a command straight to the buffer, e.g.read !ls
. Orread!!
if you think you want the output of the last executed command. foldopen
,foldclose
,folddoopen
/foldd
andfolddoclosed
/folddoc
open and close folds and execute commands on opened or closed folds. The first two also take a postfix!
to recursively open or close folds.'<,'>!tac
reverses all highlighted lines, or!iptac
as a motion to reverse lines in a paragraph:drop file.md
opensfile.md
if it is not open, otherwise switches to the window in which it is open.:+5t.
to copy the line 5 lines down, to below the current line:+5m.
to move the line 5 lines down, to below the current line<c-v>
in insert mode lets you insert character codes such as when you need a key code for escape for a:normal
command- two nvim instances can share registers by writing the shada file in one and reading it in the other.
center
,right
, andleft
let you align text.expr = true
mapping will evaluate the rhs, so that a keymap withvim.fn.expand("%")
is actually the current file and not where the config was first loaded.g<
can be used to see the last prompt again, in case you accidentally dismiss it.<C-g>
- show the current page line[(
- jump to the opening parenthesis, or[{
])
- jump to the closing parenthesis, or]}
Vim Search And Replace ¶
/<text>
- search after the cursor//
repeat the last search\C
- case sensitive search, e.g./cursor\C
\c
- case insensitive search- generally you may have
smartcase
set to not deal with the flags. For it to work,ignorecase
also needs to be set.
- generally you may have
?<text>
- search before the cursor??
repeat the last search
n
- look at the next occurrenceN
- look at the previous occurrence*
- go to the next occurrence of the word under cursor#
- go to the previous occurrence of the word under cursorctrl + o
- returns you to where you were before you started searchingctrl + i
- opposite to<C-o>
as this takes your forward.<C-g>
- jump to the next match while writing a search query<C-t>
- jump to the previous match while writing a search query
The /
register stores the last search.
There is a simple regex based search and replace:
:s/<text>/<text>
- replace just the first occurrence on the current line:s/<text>/<text>/g
- replace all occurrence on the current line:#,#s/<text>/<text>/g
- replace occurrences between the two # line numbers only:%s/<text>/<text>/g
- look for the first text and replace with the second text. With ag
flag after the last/
we can replace all occurrences on every line, without the flag, only the first match is replaced.:%s/<text>/<text>/gc
- prompt for confirmation when replacing, this allows selectively replacing
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:
/foo<CR>
- find what we are looking forcgn
- change the matchbar<CR>
- type and confirm our change.
- repeat our change for the next match
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.
:g/git/y A
- yank every matching line and append toA
register. A capitalized register name results in appending the matched line to it.:g/{/ .+1,/}/-1 sort
- sort everything between curly braces, e.g. sort css rules.
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.
/\v<(\w)\_s+\1>
%s/\v<(\w+)>\_s+\1/\1/g
- this pattern finds and replaces duplicate words in a row such asthe the
and replaces them with just one of them.\v
sets very magic, so that we don’t need to escape special characters<
and>
specify word borders, so that we don’t also match double letters.
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.
r
in would replace every selected character with the entered character.o
in any visual mode goes to the other end of the highlighted text. E.g. you highlight something, but realize that you wanted one more line on the other end. In block-wise mode it goes to the opposite corner.O
makes more sense for block-wise, however, it switches sides, which would be the same for character- and line-wise modes as switching witho
.c
in visual block mode replaces every line with the inserted text; line-wise mode replaces with just a single line.
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:
<C-v>
- enable block selection- select desired lines
$
- to go to the end of lineA
- append to line- write something
<ESC>
- to finish command
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.
q<register>
- records the actions in registera
q
- stop recording the macro@a
- replay the macro stored in the registera
@@
- replay the last executed macro50@a
- replay the macro stored in registera
50 timesqaq
- empty the register
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:
"wp
- paste the macro in registerw
to the buffer, alternatively,:put w
to put it after the current line.- edit the macro
"wy$
yank the macro back into thew
register. It seems that a"wdd
, (delete line to registerw
), would achieve the same thing, but this also has a newline in the end which sometimes could cause us some issues.
Alternatively, the same can be achieved just on the command line which is a bit cleaner approach.
:let @w='
<C-r><C-r>w
paste the contents ofw
register.- edit the macro
'
to close the string expression<CR>
load the string intow
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:
- The unnamed register
""
- 10 numbered registers
"0
to"9
- The small delete register
"-
- 26 named registers
"a
to"z
or"A
to"Z
- Three read-only registers
":
,".
,"%
- Alternate buffer register
"#
- The expression register
"=
- The selection registers
"*
and"+
- The black hole register
"_
- Last search pattern register
"/
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 ¶
:Git
- this is a vim-fugitive command which opens the git index file)/(
- to jump between sections<return>
- to open the file-
- toggle staging/unstaginga
- toggle staging/unstagings
- Stage the file under cursoru
- Unstage file under cursor-
- to add/reset fileU
- Unstage everythingX
- Discard the change under the cursorcc
- show git status or commitcvc
- show git status or commit with all of the changescva
- amends the current changes to the last commit:Git blame
A
- resize to show authorC
- resize to show commit columnD
- resize to show date/time columngq
- close window
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 ¶
- open a conflicted buffer
<Leader>-g
opens hydra mode for git operations- using
git-conflict.nvim
- everything is done inside the same buffer now
h
to pick our changel
to pick their changej
to pick bothk
to pick none(/)
to navigate between conflicts
- Fugitive approach
dv
on a conflicting file in Fugitive’s buffer opens a 3-way split automaticallyGvdiffsplit!
creates a vertical 3-way split, which looks the most convenientdiffget //2
gets our changesdiffget //3
gets their changes