Can I setup >> to keep the relative cursor position? For example, assuming the line is

def fun(<cursor>):

can I make >> result in

    def fun(<cursor>):


Obviously, with set startofline, the cursor goes to the beginning of the line after indenting. But even with set nostartofline, the cursor moves erratically, and changes both its relative position within the line as well as its absolute position in the line, depending on how the line looks like.

enter image description here

  • the cursor does not change position. It stays exactly at its same position. However, since the line changes, it looks like it moved. Dec 19 '18 at 10:25
  • @ChristianBrabandt For me the cursor does change both absolute and relative position with both startofline(obviously) and nostartofline (unexpectedly) , see the screencast in the updated question.
    – Bananach
    Dec 19 '18 at 10:38
  • you might check before and after the cursor position using :echo getpos('.') Dec 19 '18 at 10:41
  • @ChristianBrabandt I don't understand what you're getting at. Clearly, the cursor does not stay at the colon in the screencast (relative position) and it does not stay at column 10 (absolute position) as confirmed by the statusline and eyesight.
    – Bananach
    Dec 19 '18 at 10:50
  • well, please show the output of getpos('.') before and after indent! Dec 19 '18 at 12:14

Okay, let me first explain, what is happening here.

When indenting lines, Vim will indent the line, which means depending on the 'shiftwidth', 'tabstop', 'expandtab' and possibly also the 'vartabstop' options determine whether to insert leading tabs or blanks into the line. When the 'startofline' option is at its default value, the cursor will be moved to the first non-blank on the line.

The interesting thing is what happens, if the 'startofline' option is switched off. Well, basically the cursor stays at the exact same position, as it was before something was inserted. But since shifted right, the cursor appears to move. Now remember, that depending on a bunch of options, several blanks might actually be replaced by a single tab, so that technically the byte column might even decrease(!), while the line still appears to be shifted to the right.

You can verify this, by comparing the output of :echo virtcol('.') before and after you have (de-)indented your line.

So according to the documentation, the line will always be shifted by the amount of shiftwidth() spaces. So you need to adjust the cursor position (but not the byte column). This can be done like this:

func! Indent(ind)
  if &sol
    set nostartofline
  let vcol = virtcol('.')
  if a:ind
    norm! >>
    exe "norm!". (vcol + shiftwidth()) . '|'
    norm! <<
    exe "norm!". (vcol - shiftwidth()) . '|'

nnoremap >> :call Indent(1)<cr>
nnoremap << :call Indent(0)<cr>

This will map the << and >> in normal mode to perform the normal << and >> comamnds and will afterwards re-adjust the cursor positioning. Error management, range handling and handling e.g. visual mode has been intentionally left out. Those cases should probably be handled if a plugin is made out of it.

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.