Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

An easy way to center text between first and last non-white word in vim?

Tags:

vim

macros

center

Is there an easy way using a macro or ~10 line function (no plugin!) to center some text between the first and last word (=sequence of non-blank characters) on a line? E.g. to turn

        >>> No user serviceable parts below.               <<<

into

        >>>       No user serviceable parts below.         <<<

by balancing the spaces +/-1? You can assume no tabs and the result should not contain tabs, but note that the first word may not start in column 1. (EDIT: ... in fact, both delimiter words as well as the start and end of the text to center may be on arbitrary columns.)

like image 826
Jens Avatar asked Apr 05 '13 12:04

Jens


2 Answers

source this function:

fun! CenterInSpaces()
    let l   = getline('.')
    let lre = '\v^\s*\S+\zs\s*\ze'
    let rre = '\v\zs\s*\ze\S+\s*$'
    let sp  = matchstr(l,lre)
    let sp  = sp.matchstr(l,rre)
    let ln  = len(sp)
    let l   = substitute(l,lre,sp[:ln/2-1],'')
    let l   = substitute(l,rre,sp[ln/2:],'')
    call setline('.',l)
endf

note

  • this function might NOT work in all cases. I just wrote it quick for usual case. this is not a plugin after all

  • the codes lines could be reduced by combining function calls. but i think it is clear in this way, so I just leave it like this.

  • if it worked for you, you could create a map

  • it works like this: (last two lines I typed @: to repeat cmd call)

enter image description here

like image 165
Kent Avatar answered Sep 21 '22 15:09

Kent


You can use the :s command with the \= aka sub-replace-expression.

:s#\v^\s*\S+\zs(\s+)(.{-})(\s+)\ze\S+\s*$#\=substitute(submatch(1).submatch(3),'\v^(\s*)(\1\s=)$','\1'.escape(submatch(2),'~&\').'\2','')#

Overview

Capture the text (including white-space) between the >>> and <<< marks. Divide up the white-space on both sides of the text in half and substitute in the non-white-space text in between. This white-space balancing act is done via the regex engine's backtracking because math is hard. Lets go shopping!

Notes:

  • using \v or very magic mode to reduce escaping as this command is long enough already
  • use # as an alternative separator instead of the usual / for :s/pat/sub/ in hopes to make it slightly more readable

Matching Pattern

:s#\v^\s*\S+\zs(\s+)(.{-})(\s+)\ze\S+\s*$#...
  • :s with no range supplied only do the substitution on the current line.
  • ^\s*\S+ match the starting white-space followed by non-white-space. >>> in this case.
  • (\s+)(.{-})(\s+) match white-space followed by the "text" followed by white-space
  • 3 capture groups: 1) leading white-space, 2) the "text", and 3) trailing white-space. These will be later referenced by submatch(1), submatch(2), and submatch(3) respectively
  • .{-} is vim-speak for non-greedy matching or .*? in perl-speak
  • without the non-greedy matching the second capture group would include too much white-space at its end
  • \S+\s*$ match the non-white-space (i.e. <<<) and any trailing white-space
  • Use \zs and ze to designate the start and end of the match to be replaced

Replacement

\=substitute(submatch(1).submatch(3),'\v^(\s*)(\1\s=)$','\1'.escape(submatch(2),'~&\').'\2','')
  • \= tells vim that replacement will be a vim expression. Also allows the use of submatch() functions
  • substitute({str}, {pat}, {sub}, {flags}) Our expression will be a nested substitution
  • substitute(submatch(1).submatch(3), ...) do a substitute over the concatenation of leading and trailing white-spacing captured in submatch(1) and submatch(3)
  • The {pat} is ^(\s*)(\1\s=)$. Match some white-space followed by white-space of the same length as the first or 1 character longer. Capture both halves.
  • escape(submatch(2),'~&\') escape submatch(2) for any special characters. e.g. ~,&,\1, ...
  • The {sub} is '\1'.escape(submatch(2),'~&\').'\2'. Replace with the the escaped submatch(2) (i.e. the "text" we want to center) in between the halves of white-space, \1 and \2 from the {pat}
  • No {flag}'s are needed so ''

Usage

If you use this often I would suggest creating a command and putting it in ~/.vimrc.

command! -range -bar -nargs=0 CenterBetween <line1>,<line2>s#\v^\s*\S+\zs(\s+)(.{-})(\s+)\ze\S+\s*$#\=substitute(submatch(1).submatch(3),'\v^(\s*)(\1\s=)$','\1'.submatch(2).'\2','')#`

Otherwise use this once and then repeat the last substitution via & on each needed line.

For more help see

:h :s/
:h :s/\=
:h sub-replace-\=
:h submatch(
:h substitute(
:h escape(
:h /\v
:h /\S
:h /\{-
:h /\zs
:h &

EDIT by Kent

Don't be jealous, your answer has it too. ^_^

I didn't change the command, just cp/paste to my vim. only add |noh at the end to disable highlighting.

If execute this command, it looks like:

enter image description here

like image 41
Peter Rincker Avatar answered Sep 25 '22 15:09

Peter Rincker