0

This is a feature that notepad++ has: when in a wrapped line, the first press of <Home> sends you to the beginning of the wrapped line. The second press sends you to the beginning of the text (soft BoL), and the third sends you to the true beginning of the line (hard BoL). After that, pressing <Home> toggles between soft BoL and hard BoL.

Similarly, pressing <End> inside of a line wrap sends you to the end of the wrapped line on the first keypress, and to the true end of the line after that.

Here is an example:

1 | // Code that generates Lorem Ipsum
2 | if (true) {
3 |     return "Lorem ipsum dolor sit amet, consectetur 
        adipiscing elit, sed d* eiusmod tempor incididunt 
        ut labore et dolore magna aliqua";
4 | else { return ""; }

Thus, if my cursor was at the star (*) in the "2nd" line of line 3, I would want pressing <Home> to send me to the 'a' of "adipiscing." I know that this can be done with g^. However, I want the next keypress to send me to the 'r' of "return," which is equivalent to ^. Then, another keypress should put me before the indentation, equivalent to 0. Pressing <Home> again at this point should return me to the 'r' as if I had pressed ^. Afterwards, the cursor should toggle between 'r' (the first non-whitespace character) and the first character in the line.

If, instead, with my cursor at the star (*), I pressed <End>, it should be equivalent to g$, landing on the 't' of "incididunt." Another press should bring me to the semicolon at the end of the line.

The difficult part of this is that vim seemingly does not store the wrapped lines, meaning I can' determine where a line starts or ends.

3
  • @ kadekai from return back to virtual column 1. It would just toggle, something like the behavior of this: vi.stackexchange.com/questions/23205/…
    – ArcWand
    Oct 11 at 18:53
  • Yes, the former.
    – ArcWand
    Oct 11 at 19:26
  • For future readers looking for similar but not identical functionality, there are a couple of related questions here and here.
    – Rich
    Oct 12 at 15:48

1 Answer 1

1

This stackexchange post was useful. Using match(getline('.'), '\S'), it's possible to figure out the location of the end of the whitespace.
From the same stackexchange that linked in the question, I found the WinTextWidth() function helpful:

function WinTextWidth()
    let winwidth = winwidth(0)
    let winwidth -= (max([len(line('$')), &numberwidth]) * (&number || &relativenumber))
    let winwidth -= &foldcolumn
    redir => signs
    execute 'silent sign place buffer=' . bufnr('%')
    redir END
    if signs !~# '^\n---[^\n]*\n$'
        let winwidth -= 2
    endif
    return winwidth
endfunction
function GoBoL()
    let winwidth = WinTextWidth()
    let whitelen = strdisplaywidth(getline('.')[:match(getline('.'),'\S')-1])

    if virtcol('.') % winwidth - whitelen == 1
        return virtcol('.') > winwidth ? '^' : '0'
    else
        return 'g^'
    endif
endfunction
noremap <expr> <Home> GoBoL()

The GoBoL() function handles going to the beginning of the line (note that G, B, and L are all capitals!). We want to use either a soft BoL (^) or hard BoL (0) if the column of the cursor is on the first column of text. We use virtcol('.') to get the position of the cursor so that tabs and such are counted (<Tab> is usually ~4 characters wide, but only counts as one character). This position is then taken modulo the width of the window to get the column relative to the wrapped line—virtcol('.') only returns the position relative to the start of the line. In this case, it means that the if the cursor is on the 'a' in "adipiscing", virtcol('.') == 59, while virtcol('.') % winwidth == 5 (there are 4 whitespaces before it, making 'a' the 5th character). We then subtract the length of the whitespace to determine if it is equal to the 1st position.

If the cursor is not in the 1st column of a wrapped line, then we can just use g^ to achieve the desired result. Otherwise, we have to consider if the cursor is already in the first wrapped line or not. If it is, then we use 0; otherwise, we'll use ^.

End of line is similar:

function GoEoL()
    let winwidth = WinTextWidth()

    if virtcol('.') % winwidth == 0
        return '$'
    else
        return 'g$'
    endif
endfunction
nnoremap <expr> <End> GoEoL()

It's a bit simpler, since we aren't asking for it to also account for whitespace. Again, we use the virtual column modulo the width of the window to determine if the cursor is at a position; this time, checking for the end of the line.

Note that the example given, and the functions I've made here are for a case where set formatoptions=l (meaning that wrap doesn't split words) and set breakindent (meaning that the indentation of wrapped lines will match the indentation level). This should work as is even with a different formatoptions setting; vim actually has the virtual column of the last position of each wrapped line be consistent with the width of the screen. If nobreakindent is set instead, then delete any use of whitelen in the GoBoL() function, and everything should work fine.

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.