Last week, I turned off 'number' and have been using only 'relativenumber', plus a line/column count in my status line. I don't really look at line numbers that much.

But now I've had a radical idea.

I'd like to turn off numbering completely, and only turn it ('relativenumber') back on

  • in visual mode, so I can jump around quickly
  • on the ex command-line, so I can build ranges
  • in operator-pending mode, so I can provide motions like 3j

I can handle the command-line stuff with CmdlineEnter and CmdwinEnter (and corresponding exits).

Visual mode doesn't seem to have a VisualEnter event (unlike Insert mode), so I would probably have to map v, V, and <C-v> in normal mode (also gv...).

But I don't want to remap every operator—it's simply not practical.

Is there a way to detect operator-pending mode, trigger some commands, and either resume mode, or use feedkeys() to re-trigger it? Or is this attempt ill-fated?

1 Answer 1


It's a hack, but you could try this:

let [s:last_mode, s:last_state] = ['n', '']
let s:waiting_time = 10
call timer_start(s:waiting_time, {-> s:fire_missing_events()}, {'repeat': -1})

fu! s:fire_missing_events() abort
    if reg_executing() isnot# '' | return | endif
    let [mode, state] = [mode(), state()]
    if s:last_mode is# 'n'
    \ && index(['v', 'V', "\<c-v>"], mode) != -1
    \ && exists('#User#VisualEnter')
        do <nomodeline> User VisualEnter
    elseif s:last_mode isnot# 'n'
    \ && mode is# 'n'
    \ && exists('#User#NormalEnter')
        do <nomodeline> User NormalEnter
    elseif s:last_state !~# 'o'
    \ && state =~# 'o'
    \ && exists('#User#OperatorPendingEnter')
        do <nomodeline> User OperatorPendingEnter
    elseif s:last_state =~# 'o'
    \ && state !~# 'o'
    \ && exists('#User#OperatorPendingLeave')
        do <nomodeline> User OperatorPendingLeave
    let [s:last_mode, s:last_state] = [mode, state]

augroup set_rnu
    au User OperatorPendingEnter,VisualEnter setl rnu | redraw
    au User OperatorPendingLeave,NormalEnter setl nornu
    au CmdlineEnter : if state() !~# 'm' | setl rnu | redraw | endif
augroup END

It requires the patch 8.1.2047 to be able to invoke the state() function.

I briefly tested the code by writing it in /tmp/vimrc, and then running:

$ vim -Nu /tmp/vimrc +'norm! 14G' <(curl -Ls https://raw.githubusercontent.com/tmux/tmux/master/tools/UTF-8-demo.txt)

It seemed to do what you want:

enter image description here

But I don't know how reliable it is.

If you notice a too big impact on Vim's performance, try to increase the value of s:waiting_time

For more information, see:

:h User
:h reg_executing(
:h mode(
:h state(
:h exists(
:h timer_start(
:h :do
  • 2
    This was illustrative, to say the least. But there are weird interactions with 'timeout', 'timeoutlen', and mappings. For normal operators (d, etc.), with timeout on, timeoutlen=100 gives something similar to your gif. For mappings, their now impossible to complete (they timeout)! Increasing timeoutlen delays the relnums showing up (which may be desirable, who knows). Thanks for the effort; I'll wait to see if anyone comes up with something better, but while this answers the question, it's probably not going to work long-term for me. Thanks!
    – D. Ben Knoble
    Nov 12, 2019 at 18:21
  • 1
    Very cool, learned a lot.
    – dedowsdi
    Nov 13, 2019 at 0:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.