# From Acolyte to Adept: The Next Step After NOP-ing Arrow Keys in Vim

We all know about no-op’ing arrow keys in Vim to get us to break the habit of relying on them for inefficient movement. But, as this post points out, it is not the location of the arrow keys that makes them inefficient, but the modality of the movement: single steps in insert mode is a horrible way to move around when normal mode provides so much better functionality.

But here is the thing: while normal mode provides for much better and more efficient ways to move around than insert mode, it also provides for ways to move that are just as inefficient as arrow keys. In fact, there is nothing that makes, e.g. “j” significantly better than “<DOWN>”, and so if we replace <DOWN><DOWN><DOWN><DOWN> with jjjj, we are just slapping on some fresh paint on a rusty bike and calling it “faster”. We have not even replaced one bad habit with another, we are indulging in the same bad habit, albeit with a different “it”. A bad habit that is not only inefficient, but, perhaps a much worse sin in the Vim-world, inelegant.

This customization will help break you of that habit by forcing you to enter a count for each of the basic moves (”h”, “j”, “k”, “l”, “gj”, “gk”). This itself will make you more efficient for any move of three repeats or more: “3j” is more efficient than the uncounted “jjj” equivalent. But, in addition, it will also have the side-effect of making you come up with more efficient moves yourself: as your eyes focus on the point where you want to go, instead of counting off the lines (or reading off the line count if you have “:set relativenumber”), you might find it more natural to, e.g. “/<pattern>” .

In fact, you might find that in many cases, you do not even need to actually move as such. For example, instead of moving to a line 8 lines down and deleting it, “8jdd”, you could just “:+8d”. Or instead of moving to a line four lines up, yanking it, moving back to where you were and pasting it, “4kyy4jp”, you can just “:-4y” and “p”. Once you get good enough at it, it will seem like magic the way you can manipulate lines far from your current position without moving! And what you will find is that, beyond the increased efficiency in number of keystrokes, there is an increase in mental efficiency: the microseconds of visually re-orienting yourself after each move is no longer a cost that you have to pay over and over and over again.

Naturally, you are going to find things less efficient and less elegant at first. But that is just the pain that of stressing out mental muscles that have not been exercised enough, like that first leg day after the holidays (or maybe even the first leg day ever after signing up at the gym 4 years ago).

But more than efficiency, the elegance of your moves will eventually increase as well. Dramatically. As far as editing text goes, at any rate.

So, stick the following into your “~/.vimrc”, and be prepared for some pain and frustration and swearing and clumsiness as you retrain your muscle memory and your mind, before gaining a new level of enlightened “acting-without-doing”.

[NOTE]: One of the greatest impediments to me naturally working with counted-movements was the fact that counting the number of lines to go in each direction is disruptive: it completely breaks my “flow”, jarringly derailing my train of thought. See below for the solution to this, the implementation of which I consider a mandatory pre-requisite to working this way.

Gist

" Notes:
"   (1) To enhance the ergonomics of this sufficiently to make it practical, at
"       least, until your brain grows a new lobe dedicated to counting line offsets
"       in the background while you work, you should either make sure you have
"       something like the following in your ~/.vimrc:
"
"           set number
"           if has('autocmd')
"           augroup vimrc_linenumbering
"               autocmd!
"               autocmd WinLeave *
"                           \ if &number |
"                           \   set norelativenumber |
"                           \ endif
"               autocmd BufWinEnter *
"                           \ if &number |
"                           \   set relativenumber |
"                           \ endif
"               autocmd VimEnter *
"                           \ if &number |
"                           \   set relativenumber |
"                           \ endif
"           augroup END
"           endif
"
"       or you have installed a plugin like
"       (vim-numbers)[https://github.com/myusuf3/numbers.vim].
"
"   (2) You might want to relax the constraint for horizontal motions, or
"       add other motions. You can customize the list of motions by
"       specifying the keys in the
"       g:keys_to_disable_if_not_preceded_by_count variable. For example,
"       the following only enforces count-prefixed motions for "j" and "k":
"
"         let g:keys_to_disable_if_not_preceded_by_count = ["j", "k"]

function! DisableIfNonCounted(move) range
if v:count
return a:move
else
" You can make this do something annoying like:
" echoerr "Count required!"
" sleep 2
return ""
endif
endfunction

function! SetDisablingOfBasicMotionsIfNonCounted(on)
let keys_to_disable = get(g:, "keys_to_disable_if_not_preceded_by_count", ["j", "k", "l", "h"])
if a:on
for key in keys_to_disable
execute "noremap <expr> <silent> " . key . " DisableIfNonCounted('" . key . "')"
endfor
let g:keys_to_disable_if_not_preceded_by_count = keys_to_disable
let g:is_non_counted_basic_motions_disabled = 1
else
for key in keys_to_disable
try
execute "unmap " . key
catch /E31:/
endtry
endfor
let g:is_non_counted_basic_motions_disabled = 0
endif
endfunction

function! ToggleDisablingOfBasicMotionsIfNonCounted()
let is_disabled = get(g:, "is_non_counted_basic_motions_disabled", 0)
if is_disabled
call SetDisablingOfBasicMotionsIfNonCounted(0)
else
call SetDisablingOfBasicMotionsIfNonCounted(1)
endif
endfunction

command! ToggleDisablingOfNonCountedBasicMotions :call ToggleDisablingOfBasicMotionsIfNonCounted()
command! DisableNonCountedBasicMotions :call SetDisablingOfBasicMotionsIfNonCounted(1)
command! EnableNonCountedBasicMotions :call SetDisablingOfBasicMotionsIfNonCounted(0)

DisableNonCountedBasicMotions

## Displaying Relative Numbers vs. Absolute Numbers

I find the need to count line offsets before every move or operation as conducive to my “flow” as having an air horn stuffed down my throat while frozen mayonnaise is blasted into my ears. This was why I resisted count-based ergonomics in Vim for so long. Vim has a feature, “:set relativenumber” that shows relative numbers, and this makes things tremendously better, in that you can simply read of the line offset to your navigation target .… except that you must choose to show either relative numbers or absolute numbers. The fact is, the only time relative numbers are useful is for motions/operations in the current window or split, but when you have other splits open, relative numbers are worse than useless, as you need to have absolute numbers to make sense of what part of the buffer you are seeing in the non-focal splits. Showing both absolute and relative numbers at the same time would be ideal, but Vim does not support that natively (there is a plugin to help with that, but it exploits the “sign” feature, which can be a problem if you use signs to display something else, like marks, as I do). So the dilemma is that in most cases you want absolute numbers, but count-based motions/operations in the current window are annoying and mentally-disruptive if you do not have relative numbers showing to avoid you breaking your train of thought to count the lines to the target.

Luckily, a Vim plugin provides the answer: vim-numbers. This plugin automatically sets relative numbers on for the split/window in focus, and restores the previous numbering (absolute in my case) when focus moves to another split or window. It was this that made my move to strict count-required based motion possible.

EDIT: It was pointed out by /u/VanLaser that the following in your “~/.vimrc” is sufficient to achieve the relative-numbering-in-focal-window-and-absolute-everywhere-else dynamics without the need for a plugin:

set number
if has('autocmd')
augroup vimrc_linenumbering
autocmd!
autocmd WinLeave *
\ if &number |
\   set norelativenumber |
\ endif
autocmd BufWinEnter *
\ if &number |
\   set relativenumber |
\ endif
autocmd VimEnter *
\ if &number |
\   set relativenumber |
\ endif
augroup END
endif
Share