Vim: Making those Arrow Keys Work With You (or Why the Anti-Arrow-Key Propaganda is Wrong ... )

Submitted by Jeet Sukumaran on

Site Section: 

  • Computing

Keywords: 

  • Python
  • Vim
  • Zen

The Great Controversy

A standard dictum amongst experienced Vim users is not to use the arrow keys to move around your document. This dictum is often repeated again and again, in tones that range from the taken-for-granted to hysterical-zeal. The most common reason given for this is that using the arrow keys takes your hands away from the home row of your keyboard, and thus is wasteful both in terms of time and energy, whereas the standard Vim movement keys --- `h`, `j`, `k`, and `l` --- keep your hands on the home row, and therefore is far more efficient. Here is what I have discovered: this anti-arrow key argument is wrong!

The Great Experiment

When I first started using Vim, I of course found the `h`, `j`, `k`, and `l` keys to be horribly unintuitive. But what was worse was that I could not use these keys to move in insert mode. It was this latter issue that kept me resolutely married to my arrow keys. The centi-seconds of time it took for me to move my fingers to hit an arrow key seemed far more efficient than exiting into normal mode, using the `h`, `j`, `k`, and `l` to move to where I wanted, and then `i` again to get back into insert mode. And so, for the longest time, not only did I continue to use the arrow keys, but I had absolutely no desire to cease doing so, just to gain what would probably accumulate to be about an hour's worth of saved time over the course of a year.

Recently, however, on a whim, just to see what the fuss was about and if I was correct in viewing the arrow-keys-Noooooooooo!-zealots as the computing equivalent of monodimensional cladist fundamentalists, I decided to indulge in what I initially viewed as a temporary experiment in masochism.

Yes, that's right.

I disabled my arrow keys:

  inoremap  
  inoremap  
  inoremap  
  inoremap  
  noremap   
  noremap   
  noremap   
  noremap   

It did not take long (less than an hour) for me to get used to the spatial directions mapped to the `h`, `j`, `k`, and `l` keys, and so within a day I was comfortable using these keys to move around in normal mode. But the inability to move around in insert mode was incredibly annoying. It got so frustrating that I considered switching back to using arrow keys permanently, but decided to stick with the pain of trying to work in this tedious and painful environment for a little longer before giving up.

The Great Results

After a couple of days, I came to the realization that I had started working with Vim in a different way. It happened gradually, and mostly unconsciously, so that I did not notice it as first. But I did begin to notice that I was no longer tripping up or stumbling quite as much when needing to move around my document. In fact, quite the opposite, I felt like I was zipping around to places much faster and much more efficiently than I ever did before when I was using the arrow keys.

This feeling was not due to the gains from those accumulated centi-seconds shaved off my editing time by not having to move off the home row so frequently. on p>

It was, instead, due to the consequence of being forced to exit insert mode when I was not actually typing text.

Being forced to return to normal mode right after I edited the text at a single point in the document (sometimes just a couple of characters, sometimes a word or two, sometimes short blocks or fragments), also resulted in me being forced to use the rich suite of powerful normal mode movement commands to get to exactly where I needed to be before resuming editing. This was like suddenly beginning to use the fifth and other gears while driving on an open highway, whereas before I had been grinding along for mile after laborious mile on first.

The most remarkable and unanticipated aspect of my new way of working with Vim was not so much my usage of the `h`, `j`, `k`, and `l` keys, and other relatively "dumb" movement keys (`0`, `$`, etc.). It was my much greater usage of "smart" movement keys and commands. Due to the shift in the dominant ecology (i.e., normal mode vs. insert mode dominant), my repertoire of idioms for movement within and across the buffer evolved, and simple up/down/left/right movements were replaced by far more efficient and powerful commands. For example, I would use `f`/`F`/`t`/`T`/`;` etc. to jump to the exact character on a line that I wanted to get to, instead of pecking away at the left/right keys to get there. I would use `:150` or `150G` to jump directly to line 150 instead of using the up/down arrow keys. I would use `/def` or `?def` to jump directly to a function line instead of tapping away a directional key like a zombie trying to signal in pseudo-Morse. I would use marks (e.g., `mm`) to register an "anchor" into the document that I could jump back to directly (by using, for example, `'m`), before moving off to temporarily work on some other part of the document.

It is true, of course, that none of these movements are in any way more efficient when, for example, to moving a couple of characters to the left or right or up or down of the current position while in insert mode. In fact, this "local neighborhood" (i.e., one or two characters/lines on either side of my current position) is exactly where the arrow keys do come into their own by allowing you to get to these places without leaving insert mode. It is only when we start dealing with positions more than a couple of offsets away from the current position that the normal mode movement keys and commands start to yield benefits.

But the fact is, while I had arrow keys enabled I simply found it difficult to consciously make it a point to limit my use of them only within the "local neighborhood" . When in insert mode, and I needed to get anywhere that I could see in the viewable buffer, without thinking about it I would start tapping away at the arrow keys, even if the destination was several to dozens of lines/characters away. So while I had learned and started using all those "smart" movement commands that I am using now within the first couple of months of using Vim, I was just not using them enough or in all the places that I should been using them.

Disabling the arrow keys, while making "local neighborhood" movement significantly more cumbersome than before, taught and continues to teach me to view the document as a landscape, across which I can visit any point that I want directly and precisely through Vim's normal mode movement commands. I still have not completely internalized this perspective, and often find myself reaching for the arrow keys unconsciously while editing in insert mode. So, for the time being, until this regime has been exorcized from my muscle memory, I think I am going to keep my arrow keys disabled as motion keys.

Re-Educating the Arrow Keys to Work With You: Text Shifters Instead of Cursor Movers

However, that does mean that I have 4 decent keys on my keyboard not doing anything at all. That, to me, seems just as wasteful as any of the extra energy and time required to use them. And thus I decided to put those arrow keys to work as "text movers" rather than their default role as "cursor movers".

Editing text is like doing surgery: when you start hacking away at a bit of code, you usually open up various gashes and slashes in the surrounding area. At least with modern medical practices, it is both customary and polite to sew up these gashes and slashes, and naturally we do the same when editing code: indentation has to restored (here is a tip: see what `=` and `==` can do for you in Vim), empty lines need to be closed up, etc. etc. We probably do this dozens of times during a particular edit session, and typically it involves a lot of moving around, deleting and inserting lines all over the place. Even with the power of Vim's normal mode commands, it still takes a lot of keystrokes, tedium and hassle to achieve this.

And so I have remapped my arrow keys to do the following:

  • The `Up` arrow key deletes a blank line above the current line (a non-empty line will not be deleted), while the `Down` arrow key inserts a blank line above the current line. The result is hitting `Up` or `Down` moves the current line up or down, in both normal as well as insert mode.
  • Typing `Ctrl-Up` and `Ctrl-Down`, instead, deletes or inserts a blank line below the current line. The result is that the text below the current line moves up or down, respectively.
  • Typing `Left` de-dents the current line, while `Right` indents it. The result is that text shifts left and right, respectively.

The following code, included in your `~/.vimrc` or otherwise sourced into your session, implements this (the actual functions that do the work are copied from here):

function! DelEmptyLineAbove()
    if line(".") == 1
        return
    endif
    let l:line = getline(line(".") - 1)
    if l:line =~ '^\s*$'
        let l:colsave = col(".")
        .-1d
        silent normal! 
        call cursor(line("."), l:colsave)
    endif
endfunction

function! AddEmptyLineAbove()
    let l:scrolloffsave = &scrolloff
    " Avoid jerky scrolling with ^E at top of window
    set scrolloff=0
    call append(line(".") - 1, "")
    if winline() != winheight(0)
        silent normal! 
    endif
    let &scrolloff = l:scrolloffsave
endfunction

function! DelEmptyLineBelow()
    if line(".") == line("$")
        return
    endif
    let l:line = getline(line(".") + 1)
    if l:line =~ '^\s*$'
        let l:colsave = col(".")
        .+1d
        ''
        call cursor(line("."), l:colsave)
    endif
endfunction

function! AddEmptyLineBelow()
    call append(line("."), "")
endfunction

" Arrow key remapping: Up/Dn = move line up/dn; Left/Right = indent/unindent
function! SetArrowKeysAsTextShifters()
    " normal mode
    nmap  >>
    nnoremap :call DelEmptyLineAbove()
    nnoremap :call AddEmptyLineAbove()
    nnoremap :call DelEmptyLineBelow()
    nnoremap :call AddEmptyLineBelow()

    " visual mode
    vmap  >
    vnoremap :call DelEmptyLineAbove()gv
    vnoremap :call AddEmptyLineAbove()gv
    vnoremap :call DelEmptyLineBelow()gv
    vnoremap :call AddEmptyLineBelow()gv

    " insert mode
    imap 
    imap 
    inoremap :call DelEmptyLineAbove()a
    inoremap :call AddEmptyLineAbove()a
    inoremap :call DelEmptyLineBelow()a
    inoremap :call AddEmptyLineBelow()a

    " disable modified versions we are not using
    nnoremap  
    nnoremap  
    nnoremap  
    nnoremap  
    vnoremap  
    vnoremap  
    vnoremap  
    vnoremap  
    inoremap  
    inoremap  
    inoremap  
    inoremap  
endfunction

call SetArrowKeysAsTextShifters()

Using the arrow keys for cursor movement cripples you in terms of time and efficiency, due to the far superior alternatives found in normal mode. Using the arrow keys for shifting text, on the other hand, empowers you by wrapping up an often-used series of repetitive and stereotypical actions into a single keystroke.

Revisiting the "Don't Use the Arrow Keys!" Dictum

Modified slightly to say "Don't Use the Arrow Keys for Movement", this dictum basically holds. But I think it utterly fails to capture the real issue at hand, and the customary justification offered for it (the whole "taking your hands off the home row is wasteful") fails to make for a convincing argument.

Instead, I think that Vim tutorials, guides, advocates and propaganda everywhere should emphasize:
Don't move around the document in insert mode!

This is because normal mode offers so much more power and flexibility to get to any part of your document precisely and quickly. The "Don't Use the Arrow Keys for Movement" should be offered as a corollary to this, rather than a primary statement in its own right, because using arrow keys for movements leads you --- for psychological reasons --- to remain trapped in insert mode when you should be cruising and jetting about in normal mode.

15 Comments

Need to take it a step further (Vimperator)

Submitted by Anonymous (not verified) on

Takes a bit of doing, but using the Vimperator in Firefox can make browsing the web more productive on pages full of links and info. But it requires a bit of mind shift from edit mode to make it work with that plug-in as well.

I've learned a lot by

Submitted by Mike (not verified) on

I've learned a lot by watching other people use vim. Like, it took me an embarrassingly long time to realize what visual mode was for. I didn't even know how painful I was making things until I saw someone else slicing and dicing large blocks of code with a few keystrokes.

not 100% I agree, but glad you came around

Submitted by Rick Harding (not verified) on

It took me forever to leave the arrow keys, but I was never one to move around in insert mode. I do experience fewer typos if I keep my hands in position vs moving them around for far flung keys like the arrow keys. That combined with the fact that each keyboard (laptop, work, home) have slightly different arrow key locations/configurations makes it a bit of a mess.

I even map 'jj' to esc so that I can keep my hands on the home row for moving back to command mode from insert mode.

Anyway, glad another one's come to the hjkl side of things. In case you're interested I've got some recent screencasts on some more advanced vim features at lococast.net

What about list browsing?

Submitted by Arseny Kapoulkine (not verified) on

I tried to disable the arrows once, and did not last two days.
The problem for me is not insert mode navigation (I do not do it very much), but list navigation. I have lists in two places: insert-mode completion and FuzzyFinder-like file opening (via my own FuzzyFinder derived plugin). In both examples it's often much easier to hit up/down a couple of times instead of adding enough symbols for the completion to get the exact symbol.

The arrow mappings requires some context-sensitivity

Submitted by Jeet Sukumaran on

Hi Arseny,

My actual implementation now are not simple mappings like that given here, but a smarter ones (using functions) that are sensitive to context, window and mode. For example, when the buffer is non-modifiable/read-only, all that text-shifting stuff is disabled. The idea can be extended to support other contexts as well.

To be honest, though, now that I so rarely use the arrow keys for movement, I do find myself getting a little flustered or irritated when using plug-ins that do *not* allow the use of h/j/k/l (typically because they have been mapped to other functions) or at least `Ctrl-N` /`Ctrl-P` etc for movement.

For insert mode completion, I use `Ctrl-N` and `Ctrl-P` to move up and down. Works great especially as it takes `Ctrl-X Ctrl-N` to trigger the word-completion, which means that your finger is already on that key.

Here is some other code that I have in my `~/.vimrc` that makes it easier to use the completion menu (I particularly find that `C-Y` seems way out of my way to select a word, and so I like the `ENTER` mapping).

" Change the 'completeopt' option so that Vim's popup menu doesn't select the
" first completion item, but rather just inserts the longest common text of
" all matches; and the menu will come up even if there's only one match. (The
" longest setting is responsible for the former effect and the menuone is
" responsible for the latter.)
:set completeopt=longest,menuone

" Enter will simply select the highlighted menu item, just as  does.
:inoremap  pumvisible() ? "\" : "\u\"
:inoremap  pumvisible() ? "\" : "\u\"

" Make  work the way it normally does; however, when the menu appears,
" the  key will be simulated
"inoremap  pumvisible() ? '' : '=pumvisible() ? "\Down>" : ""'

" Simulates  to bring up the omni completion menu, then it simulates
"  to remove the longest common text, and finally it simulates
"  again to keep a match highlighted
"inoremap  pumvisible() ? '' : '=pumvisible() ? "\Down>" : ""'

`Alt up/down/left/right`

Submitted by Matt (not verified) on

`Alt up/down/left/right` seems to work well as a backup for things like dropdowns

Hey, I like the arrows

Submitted by mtucker (not verified) on

Hey,

I like the arrows deleting and adding whitespace! I mostly use emacs, so here's my implementation. I'm not an emacs lisp expert, but it seems to work, and won't clobber your match-data. I think the left and right arrow keys aren't necessary since I love the way emacs indents (it's one of the reasons stopping me from using vim more often). I'm sure there's a way to get emacs indentation with a vim plugin, but I digress. Here's the code:


(defun delete-blank-line (arg)
(save-excursion
(save-restriction
(save-match-data
(next-line arg)
(move-beginning-of-line 1)
(if (looking-at "[ \t]*$")
(kill-line))))))

(global-set-key (kbd "") #'(lambda ()
(interactive)
(delete-blank-line -1)))

(global-set-key (kbd "") #'(lambda ()
(interactive)
(save-excursion
(move-beginning-of-line 1)
(open-line 1))))
(global-set-key (kbd "M-") #'(lambda ()
(interactive)
(delete-blank-line 1)))

(global-set-key (kbd "M-") #'(lambda ()
(interactive)
(save-excursion
(next-line)
(move-beginning-of-line 1)
(open-line 1))))

Great stuff!

Submitted by gmarik (not verified) on

Wow!
Thanks for sharing!
Awesome!

Especially True on Kinesis

Submitted by Drew (not verified) on

Hello-

The point you are making regarding the real tradeoff regarding the arrow keys holds double for users of kinesis contour keyboards. Those keyboards are designed so that you can reach everything without moving your hand, so the movement reduction argument for hjkl is even less persuasive. It really is about forcing your brain to give up old bad habits and learn new good ones.

Also, I have a couple other nominations for arrow key repurposing:

Text bubbling:
vimcasts.org/episodes/bubbling-text/

Whitespace addition above and below:
www.vim.org/scripts/script.php?script_id=1590

Cheers,
Drew

you missed something

Submitted by masukomi (not verified) on

Two things:
1) 'O' will, by default, add a new blank line above the current one and 'o' will add one below so no need to remap arrow keys for that.
2) the whole argument for using letter keys for navigation breaks down when you start using other keyboard layouts. Dvorak for example. Way too many macros and existing things depend on the standard keys for navigation, but on alternate keyboard layout those are way awkward. Yes, I could remap them, but then I'd have to translate every single tweak to vm that utilizes them.

Instead I've just learned the various navigation methods that you mentioned that are better than letter or arrow keys and then tweaked my arrow keys so that they actually work as expected on line breaks.