A Vim Guide for Advanced Users
thevaluable.dev
thevaluable.dev
The thing that makes it golden is that it is a motion, and thus combined with a command, it is repeatable! So, like:
1. Search for something (either using / or *).
2. Type ”cgn” in Normal mode (i.e. change next search match) and replace what you searched for with whatever
3. Hammer on ”.” (i.e. repeat last command) to replace other matches.
This replaces, like, 97% of uses of :s and is a much more convenient way to do search/replace, especially if you use * to search for the word under the cursor. It’s the bee’s knees!
There's a vim extension for multiple cursors but it's a little buggy.
⁰https://sr.ht/~martanne/vis/ ¹http://doc.cat-v.org/bell_labs/structural_regexps/ ²http://doc.cat-v.org/bell_labs/sam_lang_tutorial/ ³http://kakoune.org/
The main purpose I've seen is basically block mode (like ctrl+v) or (iirc) repeating things on multiple hits (like n. or indeed cgn).
It's also better if you don't actually want to replace in the whole file and there's no convenient shorthand for the range.
I very rarely use :s myself, I'm always too worry that I'm going to clobber something I didn't mean to, I prefer to search and manually repeat a substitution. s can do it with with the "c" modifier but I find that using normal edit commands is both more flexible and easier to remember.
:%s//replace/gc> This replaces, like, 97% of uses of :s and is a much more convenient way to do search/replace, especially if you use * to search for the word under the cursor. It’s the bee’s knees!
I'm not really sure about that. I can definitely see it replacing :s for a small number of repetitions, but for a large number. I think using :s with a range is quicker.
Edit: typos, punctuation
To solve this I wrote a little plugin (that I finished as a plugin yesterday and uploaded just now): https://github.com/orlp/vim-quick-replace
It requires vim-repeat (and a patched one because the current one has an issue): https://github.com/orlp/vim-repeat
The nice thing is that it always jumps to the next match before replacing, and if you choose to replace the current match it immediately jumps to the next one, so that you can evaluate whether you want to replace it.
You can start in two quick ways: using the current word under the cursor or the current visual selection, so it's very flexible. For me replacing a variable name is often just putting my cursor on it, pressing space + r, typing the replacement name and then a couple .'s, n's and if I mess up some u's.
To see it in action: https://i.imgur.com/wEX6O1w.mp4
:%s/debug/dbg/c
vim will show the text which is about to be modified, and you have a prompt to confirm, skip, or quit (and some others).
And you would still need some nice mappings to substitute the word/selection underneath the cursor with :%s///gc.
(This is very unlikely to interest you, but I mention it for the sake of others. I’ve commonly seen people not really know about ranges especially, never stopping and thinking about what the % means (see `:help :%` if you don’t know), beyond possibly having encountered '<,'> by accident when pressing : in Visual mode (for explanation of these, see the help for :', '< and '>).)
You'd have to do *, then N to back to where you just were, then cgn, type your replacement, and then n., n., n., n (skip), n., etc.
With my plugin that just becomes one mapping, type your replacement, ., ., n (skip), ., etc.
It's just very smooth, which I like because I use this operation literally all the time. Hell, I'll even use it when changing a single variable, just to make sure there isn't another sneaky one in the file somewhere.
Vim can also take a command that yields a list of filenames as a launch parameter via something like `vim $(ag -l target_string)`, letting you go through each file with a `:n`, letting you complete large refactors surprisingly quickly.
Since Vim keeps an undo tree, you are guaranteed not to lose anything anyway.
Works a treat!
Thank you for the tip.
Most of the time it's more keystrokes than using :s though, so I'm not a big fan of it. But I see the appeal :)
set inccommand=nosplit
It will display the substitute directly in the buffer.
I could only think of this rather awkward sequence:
1. Yank to named register
2. Search
3. "cgn"
4. Ctrl-R to paste from named register
5. Repeat
This is actually the biggest gripe I have with vim, even after almost 10 years of constant use. Vim always seems to overwrite the default register when I don't want it to. This makes replacing multiple bits of text with one pasted value awkward. I feel like I'm missing some essential shortcut or configuration that everybody else knows. It can't just be named registers, right?
1. Yank using y without specifying a register
...
4. CTRL-R 0
Deletes and changes don't touch the 0-register. It always contains your last (unnamed) yank.
nnoremap <key> *``cgn
nnoremap <key> #``cgN
Hit <key> to change the current word and ... to repeat on the next occurrences.
It also has the downside that it doesn't match what is being shown: when I hit * on "overview" it highlights both "overview" and "Overview" (I have highlight search enabled), but cgn replaces only one of them. When the cursor is on Overview and I run "*cgnexample<esc>..." it will skip all the "Overview"s and only replace "overview".
But perhaps it's a bug in Vim since it works when I run * on the lowercase variant.
"Press space to highlight current word
nnoremap <silent><space>
\ :let @/='\<<C-R>=expand("<cword>")<CR>\>'<CR>:set hls<CR>
This works just like *, except that it leaves your cursor where it is. You can then use n/p/whatever as normal.Ooooh that is great! I use *# every time and am then annoyed when the n key goes backwards instead of forwards and then I try to remember to do #* instead and pff... ^^ This will solve those issues!
Edit: also because I try and keep my keybinds as close to default as I can, and <space> is oddly underused.
Per the help it's a feature, and it's infuriating! (From :help star)
> 'ignorecase' is used, 'smartcase' is not.
Here's your workaround:
" By default, * and # respect 'ignorecase'. This mapping forces these commands
" to search case sensitively, which is usually what you want when searching
" code with * or #. May be less good for prose.
nnoremap <silent> * /\C\<<C-R>=expand('<cword>')<CR>\><CR>
nnoremap <silent> # ?\C\<<C-R>=expand('<cword>')<CR>\><CR>It's pretty comprehensive. Personally I'd just install Doom Emacs though :)
The g and z set of commands are pretty useful in vim.
What I mean is: if I'm on a (say) a variable name, and I change it with "cw", then realize I actually need to change it in a few more places, I can use a little shortcut to change it in those places, without even thinking of it beforehand.
The shortcut is:
" Make <leader>. repeat the last "change word" command.
nnoremap <leader>. :let @/=@"<cr>/<cr>cgn<c-r>.<esc>
After any change command, just hit <leader>. to repeat it. You can thereafter hit "." to repeat again.Things that have nothing to do with vim but are inspired by vim are even sprouting up! And apparently it's OK to name something based on vim: https://groups.google.com/g/vim_use/c/EnSMrx_rGg4/m/d_nVlmLQ...
Edit: In Bram's reply he mentioned the household cleaner. Apparently it's similar to Ajax. That's so awesome. https://en.wikipedia.org/wiki/Vim_(cleaning_product)
I use vim inside VS code as my main editor and love the experience. It was also an easy way to step into learning vim.
I also have a vim product with vim in the name (it’s practically the whole name) and haven’t had any sort of trademark issues.
I'll also note it covers both vim and neovim, with nice little asides explaining the differences where they matter.
- Find the pattern that you want to replace (the last dot is for current dir)
:grep -r 'pattern' .
- Open the quickfix window and check if matches are correct
:copen
- Find and replace the entries of the quickfix list
:cdo s/pattern/replacement/ | update
Instead, setting nocompatible and timeoutlen=1 (1ms I guess? Help file doesn't say) works fine in my terminal, I guess because it's not over any sort of network connection. I'll give this a go and see if I run into any issues when using these escape-based features (afaik I use those only rarely) over the network.
I think that the Stack Overflow answer was just saying that the default depends on 'compatible'; not that the recommended solution is to go back to 'compatible'.
set timeout timeoutlen=1000 ttimeoutlen=100 " Fix slow O inserts
I'm a bit surprised that the 'u' flag for sort wasn't mentioned. That's the most useful one in my mind and I use it almost every time.
Personally, instead of re-learning how to do stuff with vim-sepecific commands, I just use the existing command line tool, especially since I don't do this on a daily/weekly basis (but I use sort in command line pipes at least weekly). The command is literally "sort" and for unique you just pass the -u flag, e.g. select something in Vim and type :!sort -u
I'll fix that. Thanks to point it out :)
Unfortunately this doesn't help anyone because it's not as if by deleting gI from your memory you can more easily learn something else, but perhaps it's helpful for a future guide writer or perhaps someone knows why gI is useful.
Edit: The section "The Insane Expression Register" also doesn't make much sense to me. Why would I do this 'system("ls")' thing when there is '!ls'? Using vimscript instead of bash probably has advantages in, y'know, vim, but most people know their shell and probably very few people know vimscript (certainly not me) so I'm not quite clear on the advantages here.
That said I do agree that the expression register is amazing.
In the case of g-shift-I, I’d call it 2.5 keys. Assuming you press g and I with the right hand, the shift key hardly matters in terms of time. Of course, by the same logic, 0i is faster still.
For me, though, shift is annoying. The duckduckgo bangs I find a pain and I don't use them (shift+1 is fairly uncomfortable a keystroke compared to one of the symbols available without shift) but a lot of people advertise it as a handy feature. Timing the shift key correctly is something I get wrong more often than other keystrokes, perhaps because it's operated by the pinky (though letters like "p" in operate and "a" in and are also pinky-operated and those work fine for me, idk). For me g-shift-i it's really 3 keys, not 2.5. I'd even go so far as to say that 0i is more like 1.5 than 2, since they're so nicely next to each other. By moving my right ring finger to the 0, my index finger almost automatically falls on the i. Conversely, the g and then shift makes me have to stretch my hand slightly (I find that less comfortable than shifting its position), or if I were to use the right shift, then that would be quite a big movement and stretching back to reach the i (though I rarely use the right shift: my natural hand position (to not have my wrists at an angle without needing to use a split/angled keyboard) has my right pinky in the position of the p rather than the semicolon).
So it will come.
1. a
1. b
1. c
The post says to "select in VISUAL mode the last two lines and hit `g CTRL+a`". So I guess with shift+v select the bottom two lines, and then I hit g, space, and Ctrl+a. I get: 1. a
2. b
2. c
The post says that the last line should now start with 3 instead of 1, or in my case 2. The behavior is the same as when omitting g+space for me. Am I doing it wrong?It should actually say VISUAL BLOCK mode. This is where you use `CTRL-v` to select a block of text. You can read more in `:help blockwise-visual`.
Also see the 'nrformats' option: `:help 'nrformats'`. You can include alphabetic characters to do the same thing with A-Z, for example.
Capital marks. I use ma or ms and `a `s a lot, but did not know you could do global marks with mA and `A. Very useful!
I've been using `` for a long time to mean "go back to the last place I was editing" but sometimes it gets overridden when navigating (I'm not super familiar with the jumplist, so maybe I need to get a better understanding) but `[ and `] are exactly what I've been looking for all these years.
You'll go through the change list. I think it's what you want.
I explain it in the article vim-intermediate on the same blog :)
Incidentally, the "Vimium" browser extension also supports those marks with the same syntax; they're an interesting alternative to conventional bookmarks for frequently accessed pages. https://github.com/philc/vimium/blob/master/README.md
In the first command gf, once new file opened and then closed (wq), is there a a way to close such that the previous file that was originally opened to return? At moment returning back to shell prompt.
Also curious if there is a way to open the file as a new split screen rather than replacing the entire window?
If I understand the question correctly then the answer is ":bd" after ":w"riting the file. If you just want to switch between buffers (switch between the two files) then use ":b#". You can also list open buffers with ":ls"
Someone recently told me of :tabedit, though, and that is lookin' way nicer to me. But I haven't gotten used to it yet or know how to make it work with gf or other handy things.
As for split screen, those are hotkeys I have yet to learn. If someone has an answer to that question, please reply here so the parent commenter and I both see it :)
If you want to return to the previous file without closing the new file, you can just use :bp(rev) so that the new file also stays in memory.
Relevant man page is available at :help buffers.
>Also curious if there is a way to open the file as a new split screen rather than replacing the entire window?
CTRL-W f
Another remark: vim's shortcuts are organized in a way that you are often able to guess what the correct shortcut should be. CTRL-W is the common prefix for commands regarding split windows CTRL-W v/s for splits, CTRL-W CTRL-W cycle through windows. So naturally you can guess that CTRL-W f should be open file in split and CTRL-W ] should be jump to definition in split.
Eventually I notice that I'm playing with Vim instead of finishing tasks, and go back to VSCode...
c()^[P or c(^r")
The first leaves you in normal mode the second in insert mode. I recommend vim-surround by tpope though.
Then you can try new stuff the next week, and so on. Rinse and repeat :)
So yes, I think you're right, like everybody :D
but it should say _before_ the first non-blank, which is equivalent to _after_ the last leading blank.
The stuff in this article is pretty neat but (even after using vim for about 20 years) most of it doesn't click for me as easily. Eg I don't use marks; I wouldn't think ahead to leave a mark or remember afterward which letter I used for it. Kudos to people who can gain productivity by using this stuff, but I'd need something other than an explanation of what the commands do to be effective with it. I do at least try to use the quickfix stuff; that's a pretty big productivity boost over manually going to the right file and line to fix an error spotted by my compiler.
I've mapped cnext and cprev to tab and shift-tab, to make it easy to bounce through things.
Also, in my shell I have defined
alias cbuf='vim - -c cbuffer!'
which lets me do things like "make | cbuf" or "git grep -n foo | cbuf", which is great.Tangentially, all of this plays well with some things I've built out for dealing with diffs (https://github.com/dlthomas/diff-kit) - dklocs will turn a unified diff into location-prefixed changed lines, dkcov will find the intersection of a diff and a coverage report (very useful for answering the question "how did I break that test?"), either of which can be piped right into cbuf.
I've been using VS Code + its Vim plugin recently anyway because I wanted to try out rust-analyzer and couldn't get it to work well with vim. (iirc I tried both YouCompleteMe and coc.nvim. Despite the name, the latter on classic vim; I haven't tried neovim yet. I thought they were supposed to be async, but at least one of them made vim hang for a long time when opening a fresh project, which drove me nuts.)
Neat idea also with the alias. Do you also have alias make='make 2>&1' or something? [edit: maybe make() { /path/to/make "$@" 2>&1 }] It doesn't work for me otherwise since compiler/make errors go to stderr (as they should).
Ah, no, I probably just did `make 2>&1 | cbuf`; haven't actually been on a project using make in a while.
Save modified buffer?
Y Yes
N No ^C Cancel0: https://www.ele.uri.edu/faculty/vetter/Other-stuff/vi/009-in...
Edit: add the bit about aerc
" Save session nnoremap <leader>ss :mksession! $VIMCONFIG/sessions/ " Reload session nnoremap <leader>sl :so $VIMCONFIG/sessions/
Very handy when using a terminal from within vim and looking at compiler errors/warnings or log files etc.