1

When writing LaTeX, I try to stick to one sentence per line to make my text work well with diffs, as is commonly accepted practice.

Because I have to use a very large font size, nearly every sentence gets soft-wrapped.

I also remap j and k to gj and gk, respectively, and I have a plugin that tries to keep the cursor vertically centered at all times so that I don't need to move my head. The plugin basically just maps all movement controls to themselves and adds a zz.

The problem is that zz doesn't center soft-wrapped lines, which makes this whole setup rather inconvenient, as I'm only able to vertically center the cursor every few lines, i.e., when a hard-wrapped line is reached.

Is there any way to vertically center the cursor on a soft-wrapped line?

Based on my Google results, the answer is no, but I still thought I might ask here. And if the answer really is "no", is there some fairly straightforward way to add this szz mapping to (Neo)Vim that is able to horizontally center the cursor even on soft-wrapped lines?

2
  • 1
    "I try to stick to one sentence per line to make my text work well with diffs" - while this is totally sensible, it seems to be the root cause of your troubles which involve mappings and a plugin. I'd think about whether this is worth it or if you could live with sentences (and diffs) spanning multiple lines. You might also think about formatting line breaks after you're done editing a file. Just some thoughts.
    – Friedrich
    Commented Mar 11 at 7:15
  • 2
    @Friedrich Yes, I thought about that too, but I believe it would make editing more complicated for collaborators who use a different editor that doesn't support hardwrapping, like Overleaf.
    – TheGiwi
    Commented Mar 11 at 8:43

2 Answers 2

1

EDIT: I added a second version below, which is simpler but leads to more errors for the author of the question.

I am not entirely sure if I understand your question correctly. Therefore, I restate my interpretation of your problem before proposing a potential solution below.

When pressing zz Vim centres soft wrapped lines vertically such that the middle of the hard line is at the middle of the screen, regardless of on which of the soft lines within the hard line the cursor is placed. Now, whenever you press j or k within a soft wrapped line, you move one soft line up resp. down since you have remapped them. Then, your plug-in calls zz to vertically centre the current line. (I believe, you mistakenly wrote horizontally instead of vertically in your question, right?) However, since zz does not centre with respect to the current soft line, no scrolling is happening until you enter the next hard line where zz now scrolls the window several soft lines at once. This "jump" in the scrolling is bothering you.

If this is the issue you are trying to resolve, the following function could address your needs. Additionally, you can map the function to szz. Note, that this solution does not work well with long lines that are soft wrapped around the screen boundaries.

function! SoftLineWrapAwareVerticalCenterToMidScreen ()
    " Centers the current line vertically on the window. Soft lines
    " are centered such that their middle is at the middle of the
    " window.
    execute "normal zz"

        let l:nchars = strlen(getline("."))
    let l:width = winwidth(0)
    if l:nchars <= l:width
        " There is no need to further adjust the centering
        return
    endif
    
    " The line is longer than the window width, so we need to
    " adjust the centering. We calculate the number of soft lines
    " of the current line based on the number of characters in the
    " line and the window width.
    let l:nsoftlines = nchars / l:width

    " In order to adjust the centering, first we need to calculate
    " what the soft line is to which the window was centered. 
    let l:midSoftline = l:nsoftlines / 2
    let l:midSoftline = l:midSoftline + 1

    " Now we need to calculate the soft line at which the cursor is.
    " This is the line with respect to which we want to center the 
    " window.
    let l:cursorPosV = col(".")
    let l:cursorSoftline = col(".") / l:width
    let l:cursorSoftline = l:cursorSoftline + 1

    " The difference between the soft line at which the cursor is
    " and the middle soft line is the number of soft lines we need
    " to move the window in order to center the cursor.
    let l:softlineDiff = l:midSoftline - l:cursorSoftline


    " If the difference is positive, then the cursor is above the 
    " middle of the window, so we need to move the window up.
    " Otherwise, we need to move the window down. 
    if l:softlineDiff > 0
        while l:softlineDiff > 0
            " Simulate pressing <C-y> to move the window up
            call feedkeys("\<C-y>")
            let l:softlineDiff = l:softlineDiff - 1
        endwhile
    else
        let l:softlineDiff = -l:softlineDiff
        while l:softlineDiff > 0
            " Simulate pressing <C-e> to move the window
            " down
            call feedkeys("\<C-e>")
            let l:softlineDiff = l:softlineDiff - 1
        endwhile
    endif
endfunction

map szz :call SoftLineWrapAwareVerticalCenterToMidScreen()<CR>

Following up on @TheGiwi's comment, I have came up with the following simpler solution. I believed the solution to be more stable, but it appears I was incorrect. It seems that, for @TheGiwi, the first solution works better. Like the initial solution, this one also encounters issues when long lines are soft wrapped lines around screen boundaries, resulting in errors.

function! SoftlineWrapAwareVerticalCenterToMidScreen2 ()
    " Get number of lines of the current window:
    let l:winHeight = winheight(0)

    " Get the line number of the cursor in the current window:
    let l:cursorLine = winline()

    " Middle line of the window:
    let l:midLine = l:winHeight / 2

    " Difference between the middle line of the window and the line:
    let l:lineDiff = l:midLine - l:cursorLine


    " If the difference is positive, then the cursor is above the
    " middle of the window, so we need to move the window up.
    " Otherwise, we need to move the window down.
    if l:lineDiff > 0
        while l:lineDiff > 0
            " Simulate pressing <C-y> to move the window up
            call feedkeys("\<C-y>")
            let l:lineDiff = l:lineDiff - 1
        endwhile
    else
        let l:lineDiff = -l:lineDiff
        while l:lineDiff > 0
            " Simulate pressing <C-e> to move the window down
            call feedkeys("\<C-e>")
            let l:lineDiff = l:lineDiff - 1
        endwhile
    endif
endfunction

" Added <leader> because I personally did not want to remap my `j` and `k` keys at any time. 
map <leader>j gjszz
map <leader>k gkszz
``
5
  • Oh nice! This seems to be working very well. Sometimes a "deadzone" will appear in the middle, i.e. no centering will happen for two or three lines, but I believe this is a shortcoming of zz, which I will have to solve otherwise (don't know how yet).
    – TheGiwi
    Commented Mar 11 at 8:39
  • EDIT: I believe the "deadzone" stems from zz using the number of "text lines" above and below the cursor to keep it vertically centered. Because text lines are being wrapped into multiple screen lines, this doesn't always work out. For instance, I'll sometimes have 10 screen lines above and 12 screen lines below the cursor. Depending on the text this will sometimes balance out to an even 11, resulting in an additional scroll. Does this make sense? Do you have any idea how I could circumvent this? So, ideally I would always want the same amount of softlines to be above and below the cursor.
    – TheGiwi
    Commented Mar 11 at 9:20
  • @TheGiwi I am not sure where these "deadzones" come from. The only "deadzones" I encounter are with very, very long lines. I.e. whenever Vim needs to soft wrap "too much" at screen boundaries. I.e. the line which is "too long" does not necessarily need to be at the centre of the screen. However, in such cases zz typically doesn't work either, so I may assume Vim is not designed for this. However, I added a solution that omits zz entirely. Maybe it helps.
    – Kokoro
    Commented Mar 11 at 10:04
  • Hm. The first solution appears to work better for me. Unfortunately, every line that gets wrapped in my case gets wrapped because it crosses the screen boundary. And yes, in that case I observed zz not working too. I wonder why this is so hard to implement and what changes would need to be made to the codebase to make it easier ...
    – TheGiwi
    Commented Mar 11 at 10:26
  • @TheGiwi Honestly, I am also surprised that Vim struggles with long soft wrapped lines. However, it is possible that only our understanding of Vim is insufficient to solve the problems, giving its complexity as a piece of software.
    – Kokoro
    Commented Mar 11 at 11:06
0

If you are using a recent version of Vim you could use the smoothscroll setting to get the effect you want:

set smoothscroll

Remark: It is considered that a stable implementation of smoothscroll is available with Vim 9.0.1121 available 31/12/2022 (source vim change log)

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.