Wednesday, February 11, 2009

Fixing PageUp and PageDown

For a long time it had annoyed me that pressing PageUp or PageDown would not only go to previous/next screen portion of file, but would also screw cursor position. Essentially, unlike good ol' DOS editors, in VIM pressing PageDown followed by PageUp (or vice versa) would send cursor to the start of first visible line - not back to the position where it was before. Especially as I now work more on my notebook's cramped keyboard (which btw has full sized keyboard, yet no keypad and no dedicated PageUp/PageDown keys) hitting by accident any of the keys requires some seconds to recover cursor position.

It annoyed me to the point of trying to make a VIM script to PageUp/PageDown properly.

But thankfully I have sobered up and my always questioning mind aksed: "Do you idiot think that you are the first annoyed by that? Don't you think VIM folks already have a feature for that??"

So finally I read as resourceful as ever :help - and my Eureka! awaited me right there, few lines above ":help <PageUp>". Now my ~/.vimrc has this:

map <PageUp> <C-U>
map <PageDown> <C-D>
imap <PageUp> <C-O><C-U>
imap <PageDown> <C-O><C-D>
set nostartofline

Essentially, PageUp and PageDown are now mapped to ^U and ^D respectively. The difference is that ^U/^D do preserve cursor's relative line number (relative to first visible line). ^U/^D work in normal mode, thus the ^O trick is needed to access it from insert mode.

The final nail - ":set nostartofline" - tells VIM during motion commands to try to preserve column where cursor is positioned.

You cannot possibly imagine how happy I am ;)

Update1. I felt something was wrong - but couldn't tell it immediately. Apparently, unlike PageUp/PageDown, ^U/^D move cursor only by half of screen. In other words, to emulate PageUp/PageDown, they have to be triggered twice:

map <PageUp> <C-U><C-U>
map <PageDown> <C-D><C-D>
imap <PageUp> <C-O><C-U><C-O><C-U>
imap <PageDown> <C-O><C-D><C-O><C-D>

Now that's much better.

Update2. Here goes improved version:

map <silent> <PageUp> 1000<C-U>
map <silent> <PageDown> 1000<C-D>
imap <silent> <PageUp> <C-O>1000<C-U>
imap <silent> <PageDown> <C-O>1000<C-D>

It turned out, ^U and ^D look at the counter for how many lines to scroll. Yet, they would scroll up to screen size. Thus specifying large value would make the shortcuts to scroll full screen at once. And you have to use counter all the time. Read more in :help CTRL-U and :help scroll. I'm not sure what led to the messy design (try :set scroll=NN) yet the mappings as above work fine.

With version from "Update1" especially on large files when triggering e.g. ^U^U it would be still visible that the scrolling happens in half screen steps. With the version that doesn't happen anymore as VIM now is properly told how many lines to scroll. As there is now counter involved, adding silent option is required.


Anonymous said...

Very nice - this was driving me crazy, too!

KRF said...

Thanks for the tip, also annoyed me ;)

Anonymous said...

Thanks alot :) You saved my day.

MockSoul said...
This comment has been removed by the author.
MockSoul said...


map <silent> <PageUp> 1000<C-U>
map <silent> <PageDown> 1000<C-D>
imap <silent> <PageUp> <C-O><PageUp>
imap <silent> <PageDown> <C-O><PageDown>

Makis said...

Why don't you just set nostartfile and use Ctrl-U and Ctrl-D on your laptop? I just set PageUp/Down to to force myself to learn those keys.

Ihar Filipau said...

@Makis, I think you mean 'nostartofline' which I already have set.

PgUp/PgDown vs. ^U/^D is about current on-screen line, while 'nostartofline' is about current columns after scrolling.

One needs both ^U/^D fix and 'nostartofline' for PgUp/PgDown to not to change on-screen cursor position.

Anonymous said...

thank you very much :)