6

This has been asked on stackoverflow before, but it seems it still has no solution. Perhaps that has changed with Vim 8.0.

I'd like to be able to scroll (i.e. scroll-wheel, or <C-y>/<C-e> in normal mode) by visual lines when line-wrapping is enabled, rather than by line number. This can come in handy for text documents (in my case, LaTeX files).

Any chance Vim 8.0 allows this somehow? Or can anyone suggest a vimscript function hack that might accomplish the same thing?

  • not possible as far as i know – Christian Brabandt Feb 8 '17 at 20:49
  • @ChristianBrabandt was wondering though if there was some way to cook up a vimscript function that remaps <C-y> and <C-e> (in this case, we'd want the map to be recursive/global, so would use map). Maybe there's some way to determine "how many wrapped lines does this line have?" Seems like the fact that Vim gives the option to display partial lines going on/offscreen implies there must be some convoluted way to do it. – Luke Davis Feb 10 '17 at 1:54
  • 1
    Not really, because Vim does not allow to only display partial rows. That's why smooth scrolling does not really work well in Vim. And there is nothing, VimL functions can do against it. – Christian Brabandt Feb 10 '17 at 6:20
  • @ChristianBrabandt well actually it sort of can with set display=lastline, but I see what you mean; windows must always start on the first wrapped line of a line. Seems to me that aspect is what makes this impossible. – Luke Davis Feb 13 '17 at 1:20
  • @ChristianBrabandt FWIW I came up with a hack that does (for example) as close to half-page scrolling as possible across wrapped lines, and prevents the cursor from jumping around. I now absolutely depend on it for editing LaTeX documents. Check out the answer below. – Luke Davis May 7 '17 at 20:46
3

EDIT: One problem with my original function is that you get annoying lag when using it at near the top/bottom of the document (the cursor tries to scroll up from line 1), and that the cursor does not move at all when near the top/bottom of the document.

The updated version below fixes these issues and makes scrolling more similar to the behavior of Ctrl-U and Ctrl-D. It checks whether the cursor is on line 1 or the final line; if so, calling the function does not preserve the original cursor location, and instead the cursor starts moving up the window until reaching line 1/the final line. See the code below.

function! MyScroll(num,mode)
  "First stuff
  let a:oldscrolloff=&scrolloff
  let a:oldwinline=winline()
  set scrolloff=0
  "Go to top/bottom of file, and coerce window movement with scrolling
  "(have to coerce in this way because <c-y>/<c-e> doesn't work in functions)
  let a:i=0
  if a:mode == 1
    execute 'normal! ' . eval(winline()-1) . 'gk'
      "must enclose :execute and <expr> numbers in eval()
    while line('.') != 1 && a:i<=a:num
      execute 'normal!  gk'
      let a:i+=1
    endwhile
  else
    execute 'normal! ' . eval(winheight(0)-winline()). 'gj'
    while line('.') != line('$') && a:i<=a:num
      execute 'normal!  gj'
      let a:i+=1
    endwhile
  endif
  "Fix position
  if a:oldwinline > winline()
    execute 'normal! ' . eval(a:oldwinline-winline()) . 'gj'
  elseif winline() > a:oldwinline
    execute 'normal! ' . eval(winline()-a:oldwinline) . 'gk'
  endif
  "If at top/bottom, keep scrolling by some amount
  if a:num-a:i>0
    if a:mode == 1
      execute 'normal! ' . eval(a:num-a:i) . 'gk'
    else
      execute 'normal! ' . eval(a:num-a:i) . 'gj'
    endif
  endif
  "Restore scrolloff
  execute 'set scrolloff='.a:oldscrolloff
endfunction
noremap <C-k> :call MyScroll(winheight(0)/4,1)<CR>
noremap <C-j> :call MyScroll(winheight(0)/4,0)<CR>
noremap <C-u> :call MyScroll(winheight(0)/2,1)<CR>
noremap <C-d> :call MyScroll(winheight(0)/2,0)<CR>
noremap <C-b> :call MyScroll(winheight(0),1)<CR>
noremap <C-f> :call MyScroll(winheight(0),0)<CR>
noremap <ScrollWheelUp> :call MyScroll(1,1)<CR>
noremap <ScrollWheelDown> :call MyScroll(1,0)<CR>

ORIGINAL: Managed to create a function that pretty closely matches this behavior. You can't scroll per-se exactly by line, but what you can do is scroll as close to N lines at a time as possible due to the wrapped lines, and then move the cursor back to the original relative line position in the Vim window. The function below does exactly that by coercing display adjustment to N visual ups/downs (in Vim, these are gk and gj).

I mapped Ctrl+k to scroll up, Ctrl+j to scroll down by some custom proportion of the window height, mapped ScrollWheelUp and ScrollWheelDown to line-by-line movement, and remapped C-u, C-d, C-f, and C-f to this function, which is much smoother than the default behavior in windows with many wrapped lines.

"Function
function! MyScroll(num,mode)
  "Mode==1 is scrolling up, Mode==0 is scrolling down
  let a:oldscrolloff=&scrolloff
  let a:oldwinline=winline()
  "Go to top/bottom of file, and coerce window movement with scrolling
  "have to coerce in this way because <c-y>/<c-e> doesn't work in functions
  set scrolloff=0
  if a:mode == 1
    execute 'normal! ' . eval(winline()-1) . 'gk'
    execute 'normal! ' . a:num . 'gk'
  else
    execute 'normal! ' . eval(winheight(0)-winline()). 'gj'
    execute 'normal! ' . a:num . 'gj'
  endif
  "Fix position
  if a:oldwinline > winline()
    execute 'normal! ' . eval(a:oldwinline-winline()) . 'gj'
  elseif winline() > a:oldwinline
    execute 'normal! ' . eval(winline()-a:oldwinline) . 'gk'
  endif
  execute 'set scrolloff='.a:oldscrolloff
endfunction

"Create remaps; I chose to map Ctrl+j and Ctrl+k 1/4th window height scrolling,
"and change all defaults to this scrolling method, which is better behaved
let g:scroll_fact=4
noremap <C-k> :call MyScroll(winheight(0)/g:scroll_fact,1)<CR>
noremap <C-j> :call MyScroll(winheight(0)/g:scroll_fact,0)<CR>
noremap <C-u> :call MyScroll(winheight(0)/2,1)<CR>
noremap <C-d> :call MyScroll(winheight(0)/2,0)<CR>
noremap <C-b> :call MyScroll(winheight(0),1)<CR>
noremap <C-f> :call MyScroll(winheight(0),0)<CR>
noremap <ScrollWheelUp> :call MyScroll(2,1)<CR>
noremap <ScrollWheelDown> :call MyScroll(2,0)<CR>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.