vimtricks.wiki Concise Vim tricks, one at a time.

How do I create a mapping whose behavior changes dynamically based on context?

Answer

:map {lhs} {expr}

Explanation

The <expr> flag in a mapping tells Vim to evaluate the right-hand side as an expression each time the mapping is triggered, and use the result as the key sequence to execute. This lets a single key do different things depending on the cursor position, filetype, mode, or any other condition.

How it works

nnoremap <expr> {key} {expression}
  • Every time {key} is pressed, Vim evaluates {expression}
  • The expression must return a string of key sequences
  • The returned keys are then executed as if typed

Examples

Make j move by display lines when wrapping, normal lines otherwise:

nnoremap <expr> j v:count ? 'j' : 'gj'
nnoremap <expr> k v:count ? 'k' : 'gk'

Toggle between relativenumber and no relativenumber:

nnoremap <expr> <leader>n &relativenumber ? ":set norelativenumber\<CR>" : ":set relativenumber\<CR>"

Smart Tab — indent at line start, complete elsewhere:

inoremap <expr> <Tab> col('.') <= indent('.') + 1 ? "\<C-t>" : "\<C-n>"

Tips

  • Use \<CR>, \<Esc>, \<C-w> etc. in double-quoted strings for special keys
  • The expression runs in the context of the current buffer, so &filetype, line('.'), col('.') all work
  • For complex logic, call a function: nnoremap <expr> <key> MyFunc()
  • <expr> works with all map commands: nnoremap, inoremap, vnoremap, cnoremap
  • Be careful with side effects — the expression should be pure (no : commands); use feedkeys() if you need side effects

Next

How do I run a search and replace only within a visually selected region?