I would like to use ctrl-k and ctrl-j to not only move the cursor up or down (by lines), but also to scroll the portion of the document that is visible. Effectively, leave the cursor where it is and move the document past it.

I can accomplish this for the most part using:

:nnoremap <c-j> j<c-e>
:nnoremap <c-k> k<c-y>

However, this has two issues:

  1. Minor issue: when the cursor is at the bottom visible line (L) or the top visible line (H), then an extra line is shown by ctrl-j and ctrl-k respectively (undesirable behavior).
  2. Major issue: this shortcut doesn't work with counts.

How can I improve on my solution to avoid these issues?

1 Answer 1


I was able to solve the first issue using a bit of logic in the mapping so that the j/k is emitted after <c-e>/<c-y> if the current line is the bottom/top line of the window...

:nnoremap <expr> <c-j> winline() == winheight(0) ? "\<c-e>j" : "j\<c-e>"
:nnoremap <expr> <c-k> winline() == 1 ? "\<c-y>k" : "k\<c-y>"

For the count, you can create user commands that accept counts. Should be able to combine above with that. See :h usr_40.txt and :h user-commands. (I'd help but must depart for a while.)

Update: Actually, thanks to @ChristianBrabandt we have a better, easier approach to handling counts which is to use the read-only Vim variable v:count1. This contains the count given for the last Normal command and it works with mappings. (There is also a v:count which is almost the same thing but v:count1 will contain default value 1 if no count was entered.) So that gives us:

:nnoremap <expr> <c-j> winline() == winheight(0) ? "\<c-e>".v:count1."j" : "j".v:count1."\<c-e>"
:nnoremap <expr> <c-k> winline() == 1 ? "\<c-y>".v:count1."k" : "k".v:count1."\<c-y>"

(We only prepend the count to the second motion as the first gets the count implicitly.)

  • 2
    something like this I guess: :nnoremap <expr> <c-j> winline() == winheight(0) ? "\<c-e>".v:count1."j" : "j".v:count1."\<c-e>" Note: this has still some problems when the cursor is on the last line of a buffer, because then the j will fail Feb 14, 2018 at 7:04
  • @ChristianBrabandt Oh, cool, no user command necessary? That variable is ringing some bells for me now that you mention it. As for problems with last line and j I tested it without any issues. (Or do you mean once the count is added?) Will re-check when I'm front of a computer (and try your count idea). Thanks!
    – B Layer
    Feb 14, 2018 at 7:49
  • 2
    The problem is, if the cursor is on the last line and that line is not the last window line. I guess the mapping should look like this: :nnoremap <expr> <c-j> line('.') == line('$') ? "\<c-e>" : (winline() == winheight(0) ? "\<c-e>".v:count1."j" : "j".v:count1."\<c-e>") I guess, it would be time to refactor this into a function to work correctly ;) Feb 14, 2018 at 8:05
  • @ChristianBrabandt I'm not seeing any issue with last line when it's not at the bottom of the window. It doesn't appear to behave any differently than if I hit plain j. I'll look closer but for now I've put the base mappings in.
    – B Layer
    Feb 14, 2018 at 11:00
  • 1
    @B Using this mapping: :nnoremap <expr> <c-j> winline() == winheight(0) ? "\<c-e>".v:count1."j" : "j".v:count1."\<c-e>", :h usr_toc, :norm! Gz. Note, you won't be able to scroll the content upwards, although using <c-e> it still works. This is because the j fails and the mapping is aborted. (And it works, if you go up one line and press <c-j>) Feb 14, 2018 at 11:59

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.