5

For a visual block selection, the :center command will centre the contents of the selection, not the block as a whole. Is there a way to centre the block as a whole without misaligning the contents?

Situation:

Pythagoras:

   |\
   | \
a² |  \ c²
   |   \
   |____\
     b²

Result:

                              Pythagoras:

                                   |\
                                  | \
                               a² |  \ c²
                                 |   \
                                 |____\
                                   b²

Expectation:

                              Pythagoras:

                                 |\
                                 | \
                              a² |  \ c²
                                 |   \
                                 |____\
                                   b²
2

I don't think there is a built-in way to achieve what you want to do, so I decided to try to implement it by myself. I got a working solution but this kind of text manipulation has a ton of corner cases and they are most probably not handled by my solution. Hopefully that will give you some inspiration to code your own (better) solution. Also there might already be an existing plugin to do what you want, but I don't know it.

The idea is to create a CenterFn() which will do several things:

  • Check that the last selection was a visual block (handling other kind of selections is "left as an exercise for the reader"). Caution If you copy-paste the code make sure you insert the ^V character with ctrl+vctrl+v
  • Get the content which was selected
  • Use this content to determine the size of the block (i.e. the difference between the first selected column and the furthest last one)
  • Get the size of the space to center in. Getting inspiration from :h :center we use either the value of &textwidth or 80 if &textwidth is 0.
  • Given the size of the space and the size of the block, determine how many whitespaces we need to add in front of the block
  • Add the padding

This would look like this:

function CenterFn()
    " Abort if last visual selection was not in block mode
    " Caution: The character between the quote was inserted via <C-v><C-v>
    if (visualmode() != "^V")
        echom "not visual block"
        return
    endif

    " Get the selected text lines in a list
    try
        let a_save = @a
        normal! gv"ay
        let selectedText = getreg('a', 1, v:true)
    finally
        let @a = a_save
    endtry

    " Get the first column of the selection and iterate through all lines
    " to get the len of the longest line in the selection
    let startCol = getpos("'<")[2]
    let maxLen = 0
    for line in selectedText
        let lineLen = len(line)
        let maxLen = (maxLen < lineLen ? lineLen : maxLen)
    endfor
    let endCol = startCol + maxLen

    " Get the total number of columns like :center does
    let size = (&textwidth != 0 ? &textwidth : 80)

    " Get the size of the padding
    let padSize = size/2 - (endCol - startCol)/2

    " For all the selected lines add the white space padding
    execute("'<,'>norm! " . padSize . "I ")
endfunction

And to make it more convenient we can create a user-defined command which will ease the call to the function:

command! -range Center :call CenterFn()

This way you can select your text and use :Center to get what you want. The -range parameter makes it that you can press : while you are still in visual mode which will give :'<,'>Center and the command will still work (otherwise you have to use <C-u> to get rid of the range '<,'> in the command prompt.

I tested this solution against your specific test case and it works but it would need some rework, for example in the case where you want to center a block with some text before like this:

XXXPythagoras:
XXX
XXX   |\
XXX   | \
XXXa² |  \ c²
XXX   |   \
XXX   |____\

->

XXX                     Pythagoras:
XXX
XXX                        |\
XXX                        | \
XXX                     a² |  \ c²
XXX                        |   \
XXX                        |____\
2
  • This is really neat, and example what I hoped someone would be able to conjure up. Is it possible to insert the whitespace at startCol to deal with initial text? (Although I don't usually have text before, so this is perfect.) – user33608 Feb 16 at 16:29
  • @student this is definitely possible, I just didn't take the time to do it. I have two options in mind (but there are multiple other ways to do it): either you use a substitute command (Check the :h /\%c atom to match a particular column) or you get the line in the function, modify it and use :h setline() to put the result in the buffer. – statox Feb 16 at 17:29
3

Doing it quick and dirty:

Select first column as V-BLOCK. Then press 40I<space><esc>, where "40" is number of spaces you want to add to center the picture.

Or you can use :h v_> to shift by any number of shiftwidth() to the right.

1
  • Of course, this will work. The issue is that I need to figure out how many spaces to add ... – user33608 Feb 16 at 16:24

Your Answer

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