Vim scrolling without changing cursors on-screen position

12,266

Solution 1

If you want to both move the cursor and the viewport with the cursor anywhere in the screen, perhaps you should set up some custom key bindings to do both at once.

Such as:

:nnoremap <C-M-u> j<C-e>

This will move the cursor down (j) and move the viewport (Ctrl-e) whenever you press Ctrl-Alt-u (only in normal mode).

Solution 2

There are two ways I can think of: ctrl-E and ctrl-Y scroll the buffer without moving the cursor's position relative to the window. I think that is what you want. Also, if you set scrolloff to a large number, you will get the same effect as ctrl-E and ctrl-Y with the movement keys. scrolloff setting will make it hard to get the cursor to move vertically relative to the window though. (Use something like :set so=999, so is an abbreviation for scrolloff.)

:help 'scrolloff'
:help scrolling

Solution 3

ctrl-D and ctrl-U is what you want.

ctrl-D has the same effect as 14j14<C-e> (just that the number 14 is not hard coded and the amount of movement depends on the actual size of your screen): You move the cursor several lines down in the text but the cursor stays in the middle of the screen.

Similarly ctrl-U works like 14k14<C-y>.

Addendum: If your screen has 30 lines then the the two are exactly the same.

Solution 4

There are two methods I know of. Add these lines to your .vimrc file (selecting only one of the two methods):

Method 1:

function! s:GetNumScroll(num)
  let num_rows = winheight(0)
  let num_scroll = a:num
  if (a:num == -1)
    let num_scroll = (num_rows + 1) / 2
  elseif (a:num == -2)
    let num_scroll = num_rows
  endif
  if (num_scroll < 1)
    let num_scroll = 1
  endif
  return num_scroll
endfunction

function! s:RtrnToOrig(before_scr_line)
  normal H
  let delta = a:before_scr_line - winline()
  while (delta != 0)
    if (delta < 0)
      let delta = winline() - a:before_scr_line
      let iter = 1
      while (iter <= delta)
        execute "normal" "gk"
        let iter +=1
      endwhile
    elseif (delta > 0)
      let iter = 1
      while (iter <= delta)
        execute "normal" "gj"
        let iter +=1
      endwhile
    endif
    let delta = a:before_scr_line - winline()
  endwhile
endfunction

function! s:scrollUP(num)
  let num_scroll = <SID>GetNumScroll(a:num)
  let num_rows = winheight(0)
  " -------------
  let before_scr_line = winline()
  normal L
  let after_scr_line = winline()
  let extra = num_rows - after_scr_line
  let extra += num_scroll
  " move by 1 to prevent over scrolling
  let iter = 1
  while (iter <= extra)
    execute "normal" "gj"
    let iter +=1
  endwhile
  " -------------
  call <SID>RtrnToOrig(before_scr_line)
endfunction

function! s:scrollDN(num)
  let num_scroll = <SID>GetNumScroll(a:num)
  " -------------
  let before_scr_line = winline()
  normal H
  let after_scr_line = line(".")
  execute "normal" "gk"
  let after_scr2_line = line(".")
  if ( (after_scr_line == after_scr2_line) && (after_scr_line > 1) )
    execute "normal" "gk"
  endif
  let extra = (num_scroll - 1)
  let extra += (winline() - 1)
  " move by 1 to prevent over scrolling
  let iter = 1
  while (iter <= extra)
    execute "normal" "gk"
    let iter +=1
  endwhile
  " -------------
  call <SID>RtrnToOrig(before_scr_line)
endfunction

 nmap <silent> <C-J>     :call <SID>scrollUP(1)<CR>
 nmap <silent> <C-K>     :call <SID>scrollDN(1)<CR>
 nmap <silent> <C-F>     :call <SID>scrollUP(-1)<CR>
 nmap <silent> <C-B>     :call <SID>scrollDN(-1)<CR>
 nmap <silent> <PageDown>:call <SID>scrollUP(-2)<CR>
 nmap <silent> <PageUp>  :call <SID>scrollDN(-2)<CR>

This uses the normal H, L to go to screen top, bot and the gk, gj commands to move up, down by screen line instead of actual line. Its more complicated than would seem needed just to work correctly when lines are longer than the screen width and wordwrap is on.

Or this method (which has previously been posted in vim tips wiki and on Stack Exchange):

Method 2:

" N<C-D> and N<C-U> idiotically change the scroll setting
function! s:Saving_scrollV(cmd)
  let save_scroll = &scroll
  execute "normal" a:cmd
  let &scroll = save_scroll
endfunction

" move and scroll
 nmap <silent> <C-J>           :call <SID>Saving_scrollV("1<C-V><C-D>")<CR>
 vmap <silent> <C-J> <Esc>     :call <SID>Saving_scrollV("gv1<C-V><C-D>")<CR>
 nmap <silent> <C-K>           :call <SID>Saving_scrollV("1<C-V><C-U>")<CR>
 vmap <silent> <C-K> <Esc>     :call <SID>Saving_scrollV("gv1<C-V><C-U>")<CR>

 nmap <silent> <C-F>           :call <SID>Saving_scrollV("<C-V><C-D>")<CR>
 vmap <silent> <C-F> <Esc>     :call <SID>Saving_scrollV("gv<C-V><C-D>")<CR>
 nmap <silent> <PageDown>      :call <SID>Saving_scrollV("<C-V><C-D>")<CR>
 vmap <silent> <PageDown> <Esc>:call <SID>Saving_scrollV("gv<C-V><C-D>")<CR>

 nmap <silent> <C-B>           :call <SID>Saving_scrollV("<C-V><C-U>")<CR>
 vmap <silent> <C-B> <Esc>     :call <SID>Saving_scrollV("gv<C-V><C-U>")<CR>
 nmap <silent> <PageUp>        :call <SID>Saving_scrollV("<C-V><C-U>")<CR>
 vmap <silent> <PageUp> <Esc>  :call <SID>Saving_scrollV("gv<C-V><C-U>")<CR>

The only issue I have with the second method is when lines are longer than the screen width and wordwrap is on then the cursor can move up or down some to account for the extra lines from the wrap. Also at the very top and bottom of the file the cursor can move. The first method really attempts to never move the cursor in all cases.

Solution 5

Try this mapping in .vimrc

map <ScrollWheelUp>   5<C-Y>
map <ScrollWheelDown> 5<C-E>
Share:
12,266
john-jones
Author by

john-jones

Updated on June 15, 2022

Comments

  • john-jones
    john-jones almost 2 years

    When cursor is at middle of screen and i scroll down, the cursor moves upwards on the screen. I don't want it to do that.

    How can i scroll without changing cursors on-screen position?

    Solution, added after answer:

    noremap <C-k> 14j14<C-e>
    noremap <C-l> 14k14<C-y>
    
  • Matthias 009
    Matthias 009 over 11 years
    ctrl-E does move the cursor's position relative to the window, the cursor follows the text; if the cursor is in the middle of the window/screen and you press ctrl-E repeatedly, the cursor moves to the first line; this exactly is what the asker does not want; he wants to do 14j14<C-e> just with 14 replaced by a number depending on the size of his screen
  • Matthias 009
    Matthias 009 over 11 years
    you second suggestion, setting scrolloff, does what the op asks for, but it does not give the same effect as ctrl-E; only problem with scrolloff is that it is permanent in the sense that now it is not possible to move without scrolling; it would be good to have one key go move with and one to move without scrolling
  • ciastek
    ciastek about 11 years
    More precisely: [count]<C-d> and [count]<C-u> is what he wants.
  • Big McLargeHuge
    Big McLargeHuge over 9 years
    Upvote for the so option, but @Matthias009 is right.
  • Joe C
    Joe C over 7 years
    Can you please provide more detailed explanations on your answers? Otherwise it's meaningless to anyone who comes along later.
  • M Kelly
    M Kelly over 7 years
    Well, you add these lines to your .vimrc file. It is pretty simple to scroll without moving the cursor when wordwrap is off, or when it is on but lines are all shorter than the window width. But when wordwrap is on and lines are longer than the window width scrolling can move the cursor up or down some as it goes down or up by a line. The top method maintains the cursor position no matter what, but I only use it in normal mode.
  • M Kelly
    M Kelly over 7 years
    normal H moves to the top line and normal L moves to the bottom line and from there you can figure out how many screen lines to move with "gj" or "gk" to scroll a screen line and then go back to the original screen line.
  • Joe C
    Joe C over 7 years
    Alright. Do you mind editing your answer with this information rather than keeping it in a comment?
  • M Kelly
    M Kelly almost 7 years
    There is also this on vim wiki tips: vim.wikia.com/wiki/Page_up/down_and_keep_cursor_position