8

For easy horizontal scrolling in long lines, I use these mappings that work well for me:

map <m-[> 20zh
map <m-]> 20zl

Something that has always annoyed me is when the following use case happens (| is the current cursor position):

some |short line
some other line
some other line
some other line
some other line
some other line
some veeeeeeeeeeeeeeeeeeeeeeeeery looooooooooooooooooooooooong liiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine

Sometimes I want to scroll horizontally and have a quick peek at the very long line just to see if there is anything interesting in there. In GUI text editors, I would just horizontally scroll with my mouse, and scroll back. In Vim, if I try to scroll with zl, here is what happens:

..me short line|
..me other line
..me other line
..me other line
..me other line
..me other line
..me veeeeeeeeeeeeeeeeeeeeeeeeery looooooooooooooooooooooooong liiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine

I can slightly scroll for a bit (as visually demonstrated by the .. above), but then the cursor is blocked at the end of the short line. I have to explicitly position my cursor on the very long line to be able to scroll again, which is very annoying.

There is an interesting half-solution to my problem: :set virtualedit=all. It frees the cursor of any line-limit. Hitting zl a few times on the above example now works as I want (notice the "free" cursor below):

..ine          |
..ine
..ine
..ine
..ine
..ine
..ooooooooooooooong liiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine

I only need virtualedit=all for this specific use case though. Otherwise I want the cursor to behave normally and restore the option to the default virtualedit=. And I do not want to do this manually, it should be automatic: when I start scrolling right, I want it on. When I scroll left, I still need it on except when the first characters of the lines in the file become visible again.

I am thinking about something along those lines:

map <m-[> :SOME_VOODOO_VIMSCRIPT_MAGIC<cr>20zh
map <m-]> :set virtualedit=all<cr>20zl
noremap ^ :set virtualedit=<cr>^
noremap $ :set virtualedit=<cr>$

Any idea about what to do for the SOME_VOODOO_VIMSCRIPT_MAGIC part? Getting the absolute column position for the first visible left character could help, although I do not know how to do this in Vimscript (col does not seem to do what I want). Alternate ways of solving my problems are welcome too (I just don't want any line-wrapping).

1
  • I've toyed with this before and my opinion is it's unlikely to be solved satisfactorily. Commented Jun 29, 2020 at 17:29

3 Answers 3

5

Just stumbled across this question/answer, and I think it's a terrific idea, with an ingenious solution!

However, while I think the existing answer is very clever, something about it doesn't sit well with me. I don't like the fact that, while in one sense the disabling of 'virtualedit' is automatic, it's not fully automated: it relies on the user using one of a limited set of keystrokes (^, $, or <m-[>) in order to exit the special mode.

Here's my tweaked solution:

nnoremap <silent> zl :set virtualedit=all<CR>zl
nnoremap <silent> zL :set virtualedit=all<CR>zL

augroup AllowHorizontalScroll
  autocmd!
  autocmd CursorMoved * if col('.') <= len(getline('.')) | set virtualedit= | endif
augroup END

This is more fully automated, as it fires whenever the cursor moves, instead of only when particular mappings are invoked.

N.B. There are a couple of other changes, based on my personal preference:

  1. Instead of checking if the user has reached the leftmost scroll position, it just checks if the cursor has moved into a position where virtualedit=all is no longer necessary. This seems simpler to me (and doesn't require Python!), but behaves differently in some cases*.

  2. I also don't want to have to use a special mapping for entering this mode, so I've overridden the standard zl and zL keybindings for doing this. You can also/instead add a mapping for <m-]> so as not to break your muscle memory:

     nnoremap <silent> <m-]> :set virtualedit=all<CR>20zl
    

* (Unfortunately, you cannot restore your scroll behaviour simply by subbing in your SetVirtualEdit() function in the autocommand, because if the cursor is already within the bounds of the leftmost screen, it won't move when you perform a scroll command, and so the autocommand won't fire.)

5
  • 2
    The Internet is a magical place sometimes. I never knew anyone would be interested in my lonely question+answer, much less contributing and building on it 4 years after it was asked! You, sir, are a hero! Your solution is much more elegant and shorter, albeit not behaving exactly the same when scrolling left as the cursor can continue to be "free" on short lines, so you have to rely more on using ^ or $ if you want to systematically bring it back in check. This is probably just a matter of habit and preference. Commented Jun 27, 2020 at 18:10
  • Valiant attempt. Has undesirable behavior on ragged lines. CursorHold doesn't seem to improve. Commented Jun 29, 2020 at 17:22
  • @kevinlawler Could you be any more specific about what the undesirable behaviour is? I didn't notice it during my (admittedly brief) testing.
    – Rich
    Commented Jun 30, 2020 at 8:18
  • 1
    @JeromeDalbert Yes, for me, I prefer to have the cursor where I put it, rather than having it snap back into the text when you reach the left-most screen, but I can absolutely understand why you'd prefer the behaviour of your solution: if you look at it from a different direction it makes more sense your way. Thanks so much for your kind comment, though: it's super gratifying to know your answers are appreciated!
    – Rich
    Commented Jun 30, 2020 at 8:22
  • 1
    @kevinlawler Actually, I think I may have just figured out what you mean: if, while scrolled to the right, you move your cursor upwards/downwards through a long line, then with my code, virtualedit will be switched off. I can see why you might not want that. You could alter this behaviour by integrating the check for leftmost screen from my other answer. Let me know if this is the issue you're facing, and I'll add it to my answer.
    – Rich
    Commented Jun 30, 2020 at 9:29
2

I eventually came up with this solution (edited with @Rich's answer to remove an unnecessary former Python dependency).

noremap <silent> <m-[> 20zh:call SetVirtualEdit()<cr>
noremap <silent> <m-]> :set virtualedit=all<cr>20zl
noremap <silent> ^ :set virtualedit=<cr>^
noremap <silent> $ :set virtualedit=<cr>$

function! SetVirtualEdit()
  let saved_view = winsaveview()
  keepjumps normal! g0
  let is_on_leftmost_screen = col('.') == 1
  call winrestview(saved_view)

  if is_on_leftmost_screen
    set virtualedit=
  endif
endfunction
1

Orthogonally to my other answer, here's a version of SetVirtualEdit() that you can plug into your existing solution which does not require Python:

function! SetVirtualEdit() abort
  let saved_view = winsaveview()
  keepjumps normal! g0
  let is_on_leftmost_screen = col('.') == 1
  call winrestview(saved_view)

  if is_on_leftmost_screen
    set virtualedit=
  endif
endfunction

It works by moving the cursor to the left of the screen with the g0 command, and then checking if it's at the start of the buffer's line, using :keepjumps and winsaveview/winrestview to prevent the movement from affecting Vim's state.

1
  • 2
    Very smart! I can at last get rid of the slow and clunky python import vim in my .vimrc, as this works flawlessly. Editing my answer with your suggestion. Commented Jun 27, 2020 at 18:14

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.