Vim Tips Thread

This thread is for posting Vim tips. A great way to improve Vim skills is to learn a few new tricks at a time and slowly incorporate them into your workflow.

I’ll start with a few tips, and we can keep adding more in the comments as we learn them.

Here are a few related tips about % and ! in Vim:

How to run Vim without sudo

I suspect that it’s better not to run Vim with sudo. Plugins and some other things might be able to execute code as root if Vim is run as root. I open Vim without sudo and then save protected files like this:

:w !sudo tee %

Find out more about the tee command by typing man tee in a bash (or similar) terminal.

Running shell commands on your file

An exclamation point (!) will run shell commands (like in the :w !sudo tee % example above).

Say you have an unsorted list with duplicates in your editor like this:


You could sort it and remove duplicates by visually selecting all (ggVG – go to top, visually select line, go to bottom) and then running this command:


That takes the selected text and runs (!) it through the shell’s sort command and then pipes it into the uniq command. The final result should be:


You can use a variety of shell commands there.

Another quick example: read the output of a command into your current buffer:

:r !ls -alh

(It should work in Emacs’ Evil Mode too.)

Reloading the .vimrc file while editing it

% refers to the current file in Vim. (See the sudo example above for one example.)

If you’re editing your .vimrc file and want to reload it without restarting vim, you can source the file (execute the commands in it) with:

:source %

or shorthand:

:so %

% and find/replace

The % in a Vim search and replace also refers to the current file:


It means: within the file (%) substitute foo with bar globally, and confirm each change.

If you visually select some text (for example, with shift-v), you can enter command mode with the : and then perform a substitution. The percent sign (meaning the current file) won’t be there this time, but it will look more like this:


You can also limit substitutions by line numbers. So the following command would perform a search and replace to just lines 20-25:


It can be fun to play around with ex too. (Command mode in Vim uses ex.) I think that seeing the older way of doing things with ex makes Vim’s way of doing things seem less strange. To print the file you can type %p and the same substitution commands (:3,4/s/t/TTTTT/g) work:

To open a file in ex do something like:

$ ex my-file.txt

To switch from ex to Vim, type :visual.

Another quick example: read your software versions into a README file:

:r !ruby -v
:r !rails -v

…in order to write something like this without leaving the editor:

## Versions

* Ruby 2.4.1
* Rails 5.1.4

(Pausing Vim is easy too: ctrl-z to put it to sleep, and fg at the command line to revive it.)

I just saw this site mentioned today:

I’ve been looking for a better way to navigate files than nerdtree or the ctrl-p plugin. I was going to try unite, but then found out that this it’s being abandoned for denite.

It looks interesting so far. I installed it with Vim plug and then added one line in my .vimrc file to enable searching for files with ctrl-p:

nnoremap <C-p> :<C-u>Denite file_rec<CR>

My next step will be to read: :h denite.

Example of searching for files with the ctrl-p shortcut:

There are more tips here:

and a video that I haven’t looked at yet, unfortunately only in Japanese:

I saw this on HN – it allows you to make HTTP requests in Vim:

It got me thinking that it’s pretty easy to do in default Vim without any plug-in.

Type this on a blank line in Vim:


Then press Esc to go into normal mode.

Press yy to “yank” (copy) the link into a register.

Then type:

:r !<C-r>"

where <C-r> is ctrl-r.

(C-r " will paste the contents of that register, which contains the yanked text of the curl request.)

Alternatively, you could type the following from normal mode, though it’s more difficult to edit the curl command once you enter command-line mode (as soon as your press : you enter command-line mode):

:r !curl

Then press Enter to load the response in the Vim buffer.

An example POST request with curl:

curl -X POST -H "Content-Type: application/json" -d '{"title": "Saluton Mondo", "body": "Lorem ipsum", "userId": 127}'

Here’s a quick example:


I was trying to figure out how to close all open buffers at once, without quitting, and came across this post.

The command is:

:buffdo bd

Here is a list of other commands mentioned:

:bd          - deletes the current buffer, error if there are unwritten changes
:bd!         - deletes the current buffer, no error if unwritten changes
:bufdo bd    - deletes all buffers, stops at first error (unwritten changes)
:bufdo! bd   - deletes all buffers except those with unwritten changes
:bufdo! bd!  - deletes all buffers, no error on any unwritten changes

:bw          - completely deletes the current buffer, error if there are unwritten changes
:bw!         - completely deletes the current buffer, no error if unwritten changes
:bufdo bw    - completely deletes all buffers, stops at first error (unwritten changes)
:bufdo! bw   - completely deletes all buffers except those with unwritten changes
:bufdo! bw!  - completely deletes all buffers, no error on any unwritten changes

:set confirm - confirm changes (Yes, No, Cancel) instead of error

:ls          - list open buffers
:b N         - open buffer number N (as shown in ls)
:tabe +Nbuf  - open buffer number N in new tab
:bnext       - go to the next buffer (:bn also)
:bprevious   - go to the previous buffer (:bp also)

I haven’t used :tabe +Nbuf – I usually open the current buffer in a new tab with ctrl-w shift-t.

If anyone hasn’t discovered it yet, gq is a useful Vim command for wrapping lines.

It lets you write long lines without thinking about the number of characters. When finished typing, visually highlight the lines (shift-v and move up or down to select the text to reformat), and then type gq to wrap it.


I also have this in my ~/.vimrc file:

" Use par for better `gq` formatting
" See
set formatprg=par
let $PARINIT = 'rTbgqR B=.,?_A_a Q=_s>|'

(gq should work in Emacs/Evil too.)

I’m writing some Python lately, and it’s useful, because PEP8 doesn’t like lines to be more than 79 characters long, and my editor complains…

I didn’t watch the video below, but I liked the auto-complete tips from the slides.

(in insert mode)

  • type ctrl-x ctrl-f to show a dropdown menu with auto-completed filenames.
  • ctrl-x ctrl-] completes from tags.
  • ctrl-x ctrl-n completes from names in the current file
  • ctrl-n completes for anything specified by the “complete” option

ctrl-p (previous) and ctrl-n (next) move up and down the menus.

The video might have more information.

I finally have Denite set up the way I want, and it’s great. I pasted my denite config from .vimrc below. RipGrep is installed.

" Denite ideas from and elsewhere.
" reset 50% winheight on window resize
augroup deniteresize
  autocmd VimResized,VimEnter * call denite#custom#option('default',
        \'winheight', winheight(0) / 2)
augroup end

call denite#custom#option('default', {
      \ 'prompt': '❯'
      \ })

call denite#custom#var('file_rec', 'command',
      \ ['rg', '--files', '--glob', '!.git', ''])
call denite#custom#var('grep', 'command', ['rg'])
call denite#custom#var('grep', 'default_opts',
      \ ['--hidden', '--vimgrep', '--no-heading', '-S'])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#var('grep', 'pattern_opt', ['--regexp'])
call denite#custom#var('grep', 'separator', ['--'])
call denite#custom#var('grep', 'final_opts', [])
call denite#custom#map('insert', '<Esc>', '<denite:enter_mode:normal>',
call denite#custom#map('normal', '<Esc>', '<NOP>',

" ctrl-v and ctrl-h to open files in vertical and horizontal splits
call denite#custom#map('insert', '<C-v>', '<denite:do_action:vsplit>',
call denite#custom#map('normal', '<C-v>', '<denite:do_action:vsplit>',
call denite#custom#map('insert', '<C-h>', '<denite:do_action:split>',
call denite#custom#map('normal', '<C-h>', '<denite:do_action:split>',
call denite#custom#map('normal', 'dw', '<denite:delete_word_after_caret>',

" ctrl-n and ctrl-p to move down and up the denite menus
call denite#custom#map('insert', '<C-n>', '<denite:move_to_next_line>', 'noremap')
call denite#custom#map('insert', '<C-p>', '<denite:move_to_previous_line>', 'noremap')

" ctrl-p to find files
nnoremap <C-p> :<C-u>Denite file_rec<CR>

" leader-s to find buffers
nnoremap <leader>s :<C-u>Denite buffer<CR>
nnoremap <leader><leader>s :<C-u>DeniteBufferDir buffer<CR>

" leader-8 to grep for current word
nnoremap <leader>8 :<C-u>DeniteCursorWord grep:. -mode=normal<CR>

" leader-/ to grep
nnoremap <leader>/ :<C-u>Denite grep:. -mode=normal<CR>
nnoremap <leader><leader>/ :<C-u>DeniteBufferDir grep:. -mode=normal<CR>
nnoremap <leader>d :<C-u>DeniteBufferDir file_rec<CR>
nnoremap <leader><leader>r :<C-u>Denite -resume -cursor-pos=+1<CR>

" leader-o to load custom menu
nnoremap <leader>o :<C-u>Denite menu<CR>

hi link deniteMatchedChar Special

" Add custom menus
let s:menus = {}

let s:menus.config_files = {
    \ 'description': 'Edit config files'
    \ }

" quick access to config files
let s:menus.config_files.file_candidates = [
    \ ['.aliases', '~/.aliases'],
    \ ['.zshenv', '~/.zshenv'],
    \ ['.zshrc', '~/.zshrc'],
    \ ['.vimrc', '~/.vimrc'],
    \ ['.i3conf', '~/.config/i3/config'],
    \ ['.i3status.conf', '~/.i3status.conf'],
    \ ['.ctags', '~/.ctags'],
    \ ['global .gitconfig', '~/.gitconfig'],
    \ ['global .gitignore', '~/.gitignore'],
    \ ['.muttrc', '~/.muttrc'],
    \ ['.npmrc', '~/.npmrc'],
    \ ['.mongojsrc.js', '~/.mongojsrc.js'],
    \ ['.psqlrc', '~/.psqlrc'],
    \ ['.pgpass', '~/.pgpass'],
    \ ['.pythonrc', '~/.pythonrc'],
    \ ['.tmux.conf', '~/.tmux.conf'],
    \ ['.tern-config', '~/.tern-config'],
    \ ['.vuerc', '~/.vuerc'],
    \ ['.xinitrc', '~/.xinitrc'],
    \ ['.Xmodmap', '~/.Xmodmap'],
    \ ['.taskbook.json', '~/.taskbook.json'],
    \ ]

" let s:menus.vim = {
"     \ 'description': 'Edit Vim config files'
"     \ }
" let s:menus.vim.file_candidates = [
"     \ ]

" let s:menus.vim.command_candidates = [
"     \ ['Split the window', 'vnew'],
"     \ ['Open zsh menu', 'Denite menu:zsh'],
"     \ ]

call denite#custom#var('menu', 'menus', s:menus)

A post was split to a new topic: Which-key for Vim and Emacs

Here’s another post with tips:

Security problem

It looks like it can be fixed by putting this in your .vimrc and updating vim/neovim:

set nomodeline

This cheatsheet looks good.

Have you seen exuberant ctags before? Navigation is identical to help file syntax, eg ctrl-} and ctrl-t will be 90% of what you need. Works better for static languages but I’ve had luck with Python and PHP as long as I’m not too clever. $ctags -R * will get you a “tags” file in the CWD and vim will automagically parse it when you open it in the same directory.

1 Like

I use them. One thing I wish I could do is get it to keep the tags files out of my project. I have a line in my default .gitignore, but sometimes I forget to keep them out of the build, or they use up a lot of hard drive space. One of my small browser extensions was 300 MB until I realized that it was copying the tags file into the build. :thinking:

I just ran this command to delete my tags files and freed up more than 3 GB on my hard drive.

$ find . -type f -name tags -exec rm -fv "{}" \;

(I’m not sure how safe the command is, because there might be other files named tags, but I haven’t broken anything with it yet.)

That seems unreasonably high:

rw-rw-r-- 1 dale dale 44K Jan 25 23:57 tags

Granted this project isn’t that large. Even a 300mb tag file seems weird. A character is probably a byte in your encoding so you’re looking at 300 million characters in a tag file? Or 3 BILLION characters on your machine. If that’s not in error, that’s just amazing.

Out of curiosity, exactly equivalent on my machine:
± |feature/dale/memoryattempt2 U:5 ✗| → wc -m tags
44928 tags

2020-01-26 00:02:48 :watch: dale-G3-3779 in ~/local/game
± |feature/dale/memoryattempt2 U:5 ✗| → ls -l tags
-rw-rw-r-- 1 dale dale 44928 Jan 25 23:57 tags

I just tried it in a new Gatsby JS site that I was using to test a Docker setup, and it went from 19 KB to 356 MB as soon as I opened the gatsby-config.js file. :grimacing:

ctags vim 356 MB

I’m guessing that it’s from the dependencies in the node_modules directory.

npm list | grep -v deduped | wc -l shows 2,097 lines of output.

Oh node, sigh

That’s amazing. I suppose if you are ctagging into the dependency code you may want to use this.

1 Like