Vim Tips

Originally written: February 10th, 2021
Last edited: October 2nd, 2022

To fulfill one of the goals laid out in Goals for 2021, I've taken to gather some tips for Vim.

It works well because I have to write a lot of C over ssh these days, so it's a perfect time for me to try and apply things I learn so I can actually remember whatever I read.

This page will probably be frequently updated in the forseeable future.

Edit: This page was updated frequently throughout 2021. No major updates are planned, although the article will see minor edits here and there.

General

  • :h(elp) is always your friend, i.e. :h gU
  • Vim commands only need the minimal number of letters to make the command unambiguous. i.e. :tab sp instead of :tab split
  • vim --version in command line or :version to see what options vim was compiled with.
  • :abbr Lunix Linux to replace "Lunix" with "Linux" whenever you mistype.
  • :make to make (of course, you can also do :!make)
  • :set makeprg=xxx to change the :make in vim.
  • Vim can do arithmetic: {count}<C-a> and {count}<C-x> to add and subtract. If you are not already not on a number, Vim will also move you to the next digit literal and apply the operation there. These features can come in handy if you want to use the . operator; i.e. to change a CSS property from 0px to -180px, you can simply just do 180<C-x>.... to repeat.
  • Setting paths: :set path+=./** will add all recursive directories, and then you can use :find to autocomplete file names.
  • Modifying root permission files: :w !sudo tee % > /dev/null
  • Spell check with :set spell. ]s and [s to navigate between errors. z= to suggest corrections to misspelled word. zg to add current word to spell file, and zw to remove it. <C-x>s in insert mode to activate auto-complete replacements for the word in question.
  • Running grep inside vim creates a quickfix list for you to jump to, which you can navigate with :cnext and :cprev.

Editing, Text Objects, Visual Mode

  • Use daW (delete a WORD) to delete a word plus its whitespace (as opposed to) diW (delete inside a WORD).
  • p for paragraph: i.e. dap: delete a paragraph.
  • Backspace-style deletions in insert mode: C-h one character, C-w one word, C-u start of line.
  • C-h and C-l to delete and add characters while under auto complete(C-n or C-p)
  • Delete directly:d\<PATTERN><CR> to delete from your cursor's position to the first occurence of PATTERN.
  • Replace mode with R: to quote Vim Documentation, each character you type replaces an existing character, starting with the character under the cursor.
  • o in Visual Mode to move to the other end of the selected text. This allows us to also extend our visual selection in the other direction.
  • gv to reselect the previous Visual Mode mode.
  • Vim text objects:i (inside) and a (around or all). t for XML tags. Example: cit to change the region inside XML tags.

Navigation

  • H, M, L respectively for top, middle, bottom of screen
  • Ctrl-U, Ctrl-D respectively for move half-page up, half-page down
  • Ctrl-B, Ctrl-F respectively for move half-page up, half-page down
  • Ctrl-I to reverse Ctrl-O (previous position)
  • % Jump to matching bracket for {}, [], ()
  • Display line movements: prefix with g. For example, gj and gk to move up and down display lines, and g0/g^ and g$ to move to the start/end of a line.
  • <C+]> to jump to a tag If you are not satisfied with your tag jump, try g<C+]>. You can also jump with :tag{keyword} or :tjump {keyword}.
  • ^ takes you to the first non-whitespace letter of a line, while 0 takes you to the start no matter what.
  • gf will open the file under your cursor.
  • Ctrl+W gf will open the file under your cursor in a new tab.
  • Marks: m{a-zA-Z} to set a mark, and `m{mark} to jump to it.

Ex Commands (Command Line Mode)

  • :t (copy), :m (move), :d (delete)
  • Ranges: 2,5d deletes lines 2~5. Can also be used in conjunction with . (current line), $ (EOF), % (current file), \pattern, or even \pattern + 2 for the first occurrence of pattern plus two lines.
  • Using normal mode: start off by doing A; to add semicolon at the end of a line. Select rest of file by doing jVG, and then run :'<,'>normal. to apply the dot operator in the selected portions. Or even easier... do :%normal A;. Oh, it can even run macros... :%normal @q.to return to the previous buffer you left (the "alternating buffer")
  • :edit <Tab> to open other files in the directory
  • q: to open the command line history. You can modify your history here and run commands by hitting <CR>.
  • Combine features! Run current file in python: :!python %
  • :term and :shell opens up shells, although these tasks are generally better delegated to a separate tmux pane.
  • :read to read the results of the command line operation to the current buffer, i.e. :read ls

Buffers

  • :ls To the list of buffers active
  • :b _ to open buffers, substitute _ with buffer name or number
  • Any fragment of the buffer name can be used to open files, i.e. :b que for requests.py
    This can be useful if you have files with different extension names, i.e. :b .h
  • num Ctrl+^ changes to buffer number.
  • Ctrl+^ or :b# to return to the previous buffer you left (the "alternating buffer")
  • :prev and :n(ext) to navigate between buffers forward and backward

Windows

  • :[v]sp(lit) {file} [Vertical] split of a buffer and open {file}. Also can be done with <C-w>s (horizontal split) and <C-w>v (vertical split).
  • <C-w><C-w> to cycle through windows
  • <C-w>c to close active window
  • <C-w>o to close all except active window
  • <C-w>+/- or :res(ize) +/- N to increase/decrease height, e.g. 20<C-w>+ or :res +20.
  • <C-w>>/< or :vert(ical) res(ize) +/- N to increase/decrease width, e.g. 30<C-w>< or :vert res +30.
  • <C-w>_ or :res(ize) to set height, e.g. 30<C-w>_ or :resize 30
  • <C-w>| or :vert(ical) res(ize) to set width, e.g. 30<C-w>| or :vert res 30
  • <C-w>_/| and :res(ize) and :vert(ical) res(ize) can be used without arguments to maximize/minimize in their respective axeses.

Tabs

  • Open vim tabbed by vim -p fst.foo snd.bar
  • Close tabs with Ctrl-w c.
  • gt for next tab, gT for previous tab, {i}gt for tab in position i
  • :tabs to show all tabs and their information
  • :tab sp(lit) opens the same window in a new tab
  • :tab ball expand each buffer to a tab of its own
  • Ctrl+W T to move current window to a new tab of its own.
  • :tabe {filename} Opens new tab with the file name.
  • :qa (or :wqa) : close all tabs and exit
  • :tabdo to apply same command to all tabs, i.e. :tabdo :q to exit all tabs
  • :tabc(lose) Close current tab page
  • :tabo(nly) to exit all tabs other than the current one. However, the tabs are still alive in the buffer list, i.e. you can see them with :ls, and revive them using :tab ball.
  • :tabm 0 Move current tab to first
  • :tabm Move current tab to last
  • :tabm {i} Move current tab to ith position
  • :tabfir(st) Go to first tab
  • :tabl(ast) Go to last tab

Matching, Searching, Substitution

  • * searches the current exact word selected.
  • g* does the same, but it will also find words that have the pattern as a substring.
  • Ctrl+r Ctrl+w will place the current word selected into the search buffer.
  • f, t, F, T allow you to jump to the first occurrence of the character to the right/left in the line. Additionally, using ; will allow you to repeat this search, or , for the other direction. You can also prefix them with a count, e.g. 3fy for the 3rd occurrence of "y" to the right.
  • A blank search // repeats the last search. Putting "e" at the end of a search, i.e. /<pattern>/e places your cursor to the end of the pattern. This also works for substitution.
  • q/ to bring up previous searches.
  • gn to work a match, e.g. gUgn to convert to upper case.
  • :%s applies the substitution command on every line and /g applies substitution for every occurrence in the line. In other words, % can be understood as for every row, and /g (misleadingly called global) can be understood as for every column.
  • \v to do very magic searches and \V to match verbatim in search/substitute. Very magic searches are similar to Perl-style regex (no weird escaping), and also allows exact match with \v<the>, for instance, if you want to search for "the" without "their", "they", etc.
  • In search/substitution, <C-r> for accessing registers, and \= for evaluating VimScript commands.
  • Advanced and rather convoluted method of swapping the word "dog" and "man" in a single pass:
    /\v(:<man>|:<dog>)
    :%s//\={"dog":"man","man":"dog"}[submatch(1)]/g
  • :g global and :v (inVerted global), e.g. :v/href/d deletes all lines that does not have "href" in it. or :g/TODO/t$ places all the TODO lines at the end of the file.

Registers

  • Access registers by " then register name, i.e. "r for the register r.
  • Example actions: "rp then "ry
  • Yanked/deleted lines stored in registers 0~9; yydd"0p pastes the yanked content. Useful if you want to yank, delete, and then put the yanked text.
  • Black hole register "_, another option for deleting text and ignoring it.
  • In insert mode, <C-r> instead of ". This can be easier, as instead of playing around with p or P for put direction, it's very clear where you are going to put your text.
  • ". has last inserted text.
  • "% has file path.
  • ": has most recently used command (i.e. :w or :%s/foo/bar ).
  • /, ?, *, # have the last searched word, i.e. /foo:%s/<Ctrl-r />/bar/gc is :%s/foo/bar/gc
  • "= expression register for dealing with results of expressions; i.e. in insert mode, <Ctrl-r />=2+2 or <Ctrl-r />=system('ls')
  • 10@ to repeat the macro in register a 10 times.

Indentation

  • = indents. For instance, gg=G the entire file.
  • == indents the current line.
  • The autoindent setting copies the indentation from the previous line. Usually recommended.
  • The expandtab setting converts tabs to softtabstop number of spaces.
  • For hard tab-indentation, keep shiftwidth and tabstop to the same value:
                    
                      set shiftwidth=4
                      set tabstop=4
                    
                  
  • If using space-based indentation (e.g. for Haskell), set expandtab and softtabstop:
                    
                      set expandtab
                      set shiftwidth=4
                      set softtabstop=4
                    
                  

Plugins

I use Vundle for my plugin manager, although that is no longer necessary with Vim8+.

Below are a list of plugins I use. I don't use all of them together, but depending on my system (i.e. local machine v.s. ssh server), I use a mixture of them.

  • Wakatime: track your coding habits.
  • Ale: asynchronous syntax checker using Vim8+'s capabilities and Language Server Protocol (LSP)'s Easily my favorite plugin. Along with the next plugin, brings in the power of an IDE at the fraction of its cost.
  • YouCompleteMe: many autocomplete plugins exist: Vim's Native autocompletion, deoplete, VimCompletesMe, but I've found YouCompleteMe to be a powerful, easy-to-setup, still sufficiently fast autocompletion engine.
  • Syntastic: syntax checker that I use for linux kernel hacking, since it has out of box support for checkpatch. Also setting up vim to work with the linux kernel can be a pain.
  • Gutentags: automated tag generation.
  • Grammarous: nifty grammar checker if you write non-code (i.e. TeX) in vim.
  • VimTeX: TeX plugin for Vim, including features like auto compilation.
  • vim-commentary: gc to comment in virtually any language.
  • vim-tmux-navigator: nice integration with tmux.

VimScript

VimScript can be quite cursed at times, so it is necessary to avoid certain pitfalls. A large part of the tips here are taken from Learn VimScript the Hard Way by Steve Losh.

  • Scoping: Vim comes with many scopes, such as global(:g), buffer(:b), local to function (:l), etc. Check out :help internal-variables for more information.
  • Function naming: unscoped functions must start with a capital letter if they are unscoped! I.e. :function meow() will not work; it needs to be :function Meow() However, function g:meow() would work.
  • Unfortunately, the == operator may not do what you expect it to do... because it may depend on user setttings. If you have :set noignorecase on, "foo" == "FOO" will evaluate to TRUE! As a general rule, instead use ==# which is the "case-sensitive no matter what the user has set" comparison operator.