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.)
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)
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:
\v
or very magic
mode to reduce escaping as this command is long enough
already#
as an alternative separator instead of the usual /
for :s/pat/sub/
in hopes to make it slightly more readableMatching 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-spacesubmatch(1)
, submatch(2)
, and submatch(3)
respectively.{-}
is vim-speak for non-greedy matching or .*?
in perl-speak\S+\s*$
match the non-white-space (i.e. <<<
) and any trailing white-space\zs
and ze
to designate the start and end of the match to be replacedReplacement
\=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()
functionssubstitute({str}, {pat}, {sub}, {flags})
Our expression will be a nested substitutionsubstitute(submatch(1).submatch(3), ...)
do a substitute over the concatenation of leading and trailing white-spacing captured in submatch(1)
and submatch(3)
{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
, ...{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}
{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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With