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); usefeedkeys()if you need side effects