1 " Align: tool to align multiple fields based on one or more separators
2 " Author: Charles E. Campbell, Jr.
5 " GetLatestVimScripts: 294 1 :AutoInstall: Align.vim
6 " GetLatestVimScripts: 1066 1 :AutoInstall: cecutil.vim
7 " Copyright: Copyright (C) 1999-2007 Charles E. Campbell, Jr. {{{1
8 " Permission is hereby granted to use and distribute this code,
9 " with or without modifications, provided that this copyright
10 " notice is copied with it. Like anything else that's free,
11 " Align.vim is provided *as is* and comes with no warranty
12 " of any kind, either expressed or implied. By using this
13 " plugin, you agree that in no event will the copyright
14 " holder be liable for any damages resulting from the use
17 " Romans 1:16,17a : For I am not ashamed of the gospel of Christ, for it is {{{1
18 " the power of God for salvation for everyone who believes; for the Jew first,
19 " and also for the Greek. For in it is revealed God's righteousness from
22 " ---------------------------------------------------------------------
24 if exists("g:loaded_Align") || &cp
27 let g:loaded_Align = "v35"
30 echo "***warning*** this version of Align needs vim 7.0"
38 " ---------------------------------------------------------------------
39 " Debugging Support: {{{1
40 "if !exists("g:loaded_Decho") | runtime plugin/Decho.vim | endif
42 " ---------------------------------------------------------------------
44 if !exists("g:Align_xstrlen")
45 if &enc == "latin1" || $LANG == "en_US.UTF-8" || !has("multi_byte")
46 let g:Align_xstrlen= 0
48 let g:Align_xstrlen= 1
52 " ---------------------------------------------------------------------
53 " Align#AlignCtrl: enter alignment patterns here {{{1
55 " Styles = all alignment-break patterns are equivalent
56 " C cycle through alignment-break pattern(s)
57 " l left-justified alignment
58 " r right-justified alignment
60 " - skip separator, treat as part of field
61 " : treat rest of line as field
62 " + repeat previous [lrc] style
63 " < left justify separators
64 " > right justify separators
67 " Builds = s:AlignPat s:AlignCtrl s:AlignPatQty
68 " C s:AlignPat s:AlignCtrl s:AlignPatQty
85 fun! Align#AlignCtrl(...)
87 " call Dfunc("AlignCtrl(...) a:0=".a:0)
89 " save options that will be changed
96 " clear visual mode so that old visual-mode selections don't
97 " get applied to new invocations of Align().
99 if !exists("s:Align_gavemsg")
100 let s:Align_gavemsg= 1
101 echomsg "Align needs at least Vim version 6.2 to clear visual-mode selection"
103 elseif exists("s:dovisclear")
104 " call Decho("clearing visual mode a:0=".a:0." a:1<".a:1.">")
105 let clearvmode= visualmode(1)
108 " set up a list akin to an argument list
110 let A= s:QArgSplitter(a:1)
118 " Check for bad separator patterns (zero-length matches)
119 " (but zero-length patterns for g/v is ok)
124 echoerr "AlignCtrl: separator<".A[ipat]."> matches zero-length string"
126 " call Dret("AlignCtrl")
134 " call Decho("AlignCtrl() A[0]=".A[0])
135 if !exists("s:AlignStyle")
136 let s:AlignStyle= "l"
138 if !exists("s:AlignPrePad")
141 if !exists("s:AlignPostPad")
142 let s:AlignPostPad= 0
144 if !exists("s:AlignLeadKeep")
145 let s:AlignLeadKeep= 'w'
149 " ----------------------
150 " List current selection
151 " ----------------------
152 if !exists("s:AlignPatQty")
155 echo "AlignCtrl<".s:AlignCtrl."> qty=".s:AlignPatQty." AlignStyle<".s:AlignStyle."> Padding<".s:AlignPrePad."|".s:AlignPostPad."> LeadingWS=".s:AlignLeadKeep." AlignSep=".s:AlignSep
156 " call Decho("AlignCtrl<".s:AlignCtrl."> qty=".s:AlignPatQty." AlignStyle<".s:AlignStyle."> Padding<".s:AlignPrePad."|".s:AlignPostPad."> LeadingWS=".s:AlignLeadKeep." AlignSep=".s:AlignSep)
157 if exists("s:AlignGPat") && !exists("s:AlignVPat")
158 echo "AlignGPat<".s:AlignGPat.">"
159 elseif !exists("s:AlignGPat") && exists("s:AlignVPat")
160 echo "AlignVPat<".s:AlignVPat.">"
161 elseif exists("s:AlignGPat") && exists("s:AlignVPat")
162 echo "AlignGPat<".s:AlignGPat."> AlignVPat<".s:AlignVPat.">"
165 while ipat <= s:AlignPatQty
166 echo "Pat".ipat."<".s:AlignPat_{ipat}.">"
167 " call Decho("Pat".ipat."<".s:AlignPat_{ipat}.">")
172 " ----------------------------------
173 " Process alignment control settings
174 " ----------------------------------
175 " call Decho("process the alignctrl settings")
176 " call Decho("style<".style.">")
178 if style ==? "default"
179 " Default: preserve initial leading whitespace, left-justified,
180 " alignment on '=', one space padding on both sides
181 if exists("s:AlignCtrlStackQty")
182 " clear AlignCtrl stack
183 while s:AlignCtrlStackQty > 0
184 call Align#AlignPop()
186 unlet s:AlignCtrlStackQty
188 " Set AlignCtrl to its default value
189 call Align#AlignCtrl("Ilp1P1=<",'=')
190 call Align#AlignCtrl("g")
191 call Align#AlignCtrl("v")
195 " call Dret("AlignCtrl")
200 " map support: Do an AlignPush now and the next call to Align()
201 " will do an AlignPop at exit
202 " call Decho("style case m: do AlignPush")
203 call Align#AlignPush()
207 " = : record a list of alignment patterns that are equivalent
209 " call Decho("style case =: record list of equiv alignment patterns")
210 let s:AlignCtrl = '='
213 let s:AlignPat_1 = A[2]
216 let s:AlignPat_1 = s:AlignPat_1.'\|'.A[ipat]
219 let s:AlignPat_1= '\('.s:AlignPat_1.'\)'
220 " call Decho("AlignCtrl<".s:AlignCtrl."> AlignPat<".s:AlignPat_1.">")
223 "c : cycle through alignment pattern(s)
225 " call Decho("style case C: cycle through alignment pattern(s)")
226 let s:AlignCtrl = 'C'
228 let s:AlignPatQty= A[0] - 1
231 let s:AlignPat_{ipat}= A[ipat+1]
232 " call Decho("AlignCtrl<".s:AlignCtrl."> AlignQty=".s:AlignPatQty." AlignPat_".ipat."<".s:AlignPat_{ipat}.">")
239 let s:AlignPrePad= substitute(style,'^.*p\(\d\+\).*$','\1','')
240 " call Decho("style case p".s:AlignPrePad.": pre-separator padding")
241 if s:AlignPrePad == ""
242 echoerr "AlignCtrl: 'p' needs to be followed by a numeric argument'
245 " call Dret("AlignCtrl")
251 let s:AlignPostPad= substitute(style,'^.*P\(\d\+\).*$','\1','')
252 " call Decho("style case P".s:AlignPostPad.": post-separator padding")
253 if s:AlignPostPad == ""
254 echoerr "AlignCtrl: 'P' needs to be followed by a numeric argument'
257 " call Dret("AlignCtrl")
263 " call Decho("style case w: ignore leading whitespace")
264 let s:AlignLeadKeep= 'w'
266 " call Decho("style case w: keep leading whitespace")
267 let s:AlignLeadKeep= 'W'
269 " call Decho("style case w: retain initial leading whitespace")
270 let s:AlignLeadKeep= 'I'
274 " first list item is a "g" selector pattern
275 " call Decho("style case g: global selector pattern")
277 if exists("s:AlignGPat")
279 " call Decho("unlet s:AlignGPat")
282 let s:AlignGPat= A[2]
283 " call Decho("s:AlignGPat<".s:AlignGPat.">")
286 " first list item is a "v" selector pattern
287 " call Decho("style case v: global selector anti-pattern")
289 if exists("s:AlignVPat")
291 " call Decho("unlet s:AlignVPat")
294 let s:AlignVPat= A[2]
295 " call Decho("s:AlignVPat<".s:AlignVPat.">")
299 "[-lrc+:] : set up s:AlignStyle
300 if style =~# '[-lrc+:]'
301 " call Decho("style case [-lrc+:]: field justification")
302 let s:AlignStyle= substitute(style,'[^-lrc:+]','','g')
303 " call Decho("AlignStyle<".s:AlignStyle.">")
306 "[<>|] : set up s:AlignSep
308 " call Decho("style case [-lrc+:]: separator justification")
309 let s:AlignSep= substitute(style,'[^<>|]','','g')
310 " call Decho("AlignSep ".s:AlignSep)
315 if !exists("s:AlignCtrl")
319 " restore search and options
323 " call Dret("AlignCtrl ".s:AlignCtrl.'p'.s:AlignPrePad.'P'.s:AlignPostPad.s:AlignLeadKeep.s:AlignStyle)
324 return s:AlignCtrl.'p'.s:AlignPrePad.'P'.s:AlignPostPad.s:AlignLeadKeep.s:AlignStyle
327 " ---------------------------------------------------------------------
328 " s:MakeSpace: returns a string with spacecnt blanks {{{1
329 fun! s:MakeSpace(spacecnt)
330 " call Dfunc("MakeSpace(spacecnt=".a:spacecnt.")")
332 let spacecnt = a:spacecnt
335 let spacecnt = spacecnt - 1
337 " call Dret("MakeSpace <".str.">")
341 " ---------------------------------------------------------------------
342 " Align#Align: align selected text based on alignment pattern(s) {{{1
343 fun! Align#Align(hasctrl,...) range
344 " call Dfunc("Align#Align(hasctrl=".a:hasctrl.",...) a:0=".a:0)
347 if string(a:hasctrl) != "0" && string(a:hasctrl) != "1"
348 echohl Error|echo 'usage: Align#Align(hasctrl<'.a:hasctrl.'> (should be 0 or 1),"separator(s)" (you have '.a:0.') )'|echohl None
349 " call Dret("Align#Align")
352 if exists("s:AlignStyle") && s:AlignStyle == ":"
353 echohl Error |echo '(Align#Align) your AlignStyle is ":", which implies "do-no-alignment"!'|echohl None
354 " call Dret("Align#Align")
358 " set up a list akin to an argument list
360 let A= s:QArgSplitter(a:1)
365 " if :Align! was used, then the first argument is (should be!) an AlignCtrl string
366 " Note that any alignment control set this way will be temporary.
367 let hasctrl= a:hasctrl
368 " call Decho("hasctrl=".hasctrl)
369 if a:hasctrl && A[0] >= 1
370 " call Decho("Align! : using A[1]<".A[1]."> for AlignCtrl")
372 let hasctrl= hasctrl + 1
373 call Align#AlignCtrl('m')
374 call Align#AlignCtrl(A[1],A[2])
375 " call Decho("Align! : also using A[2]<".A[2]."> for AlignCtrl")
377 call Align#AlignCtrl(A[1]."m")
379 call Align#AlignCtrl(A[1])
383 " Check for bad separator patterns (zero-length matches)
384 let ipat= 1 + hasctrl
387 echoerr "Align: separator<".A[ipat]."> matches zero-length string"
388 " call Dret("Align#Align")
394 " record current search pattern for subsequent restoration
397 let keep_report= &report
398 set noic report=10000
401 " Align will accept a list of separator regexps
402 " call Decho("A[0]=".A[0].": accepting list of separator regexp")
404 if s:AlignCtrl =~# "="
405 "= : consider all separators to be equivalent
406 " call Decho("AlignCtrl: record list of equivalent alignment patterns")
407 let s:AlignCtrl = '='
408 let s:AlignPat_1 = A[1 + hasctrl]
410 let ipat = 2 + hasctrl
412 let s:AlignPat_1 = s:AlignPat_1.'\|'.A[ipat]
415 let s:AlignPat_1= '\('.s:AlignPat_1.'\)'
416 " call Decho("AlignCtrl<".s:AlignCtrl."> AlignPat<".s:AlignPat_1.">")
418 elseif s:AlignCtrl =~# 'C'
419 "c : cycle through alignment pattern(s)
420 " call Decho("AlignCtrl: cycle through alignment pattern(s)")
421 let s:AlignCtrl = 'C'
422 let s:AlignPatQty= A[0] - hasctrl
424 while ipat <= s:AlignPatQty
425 let s:AlignPat_{ipat}= A[(ipat + hasctrl)]
426 " call Decho("AlignCtrl<".s:AlignCtrl."> AlignQty=".s:AlignPatQty." AlignPat_".ipat."<".s:AlignPat_{ipat}.">")
432 " Initialize so that begline<endline and begcol<endcol.
433 " Ragged right: check if the column associated with '< or '>
434 " is greater than the line's string length -> ragged right.
435 " Have to be careful about visualmode() -- it returns the last visual
436 " mode used whether or not it was used currently.
437 let begcol = virtcol("'<")-1
438 let endcol = virtcol("'>")-1
440 let begcol = virtcol("'>")-1
441 let endcol = virtcol("'<")-1
443 " call Decho("begcol=".begcol." endcol=".endcol)
444 let begline = a:firstline
445 let endline = a:lastline
447 let begline = a:lastline
448 let endline = a:firstline
450 " call Decho("begline=".begline." endline=".endline)
452 if (begline == line("'>") && endline == line("'<")) || (begline == line("'<") && endline == line("'>"))
453 let vmode= visualmode()
454 " call Decho("vmode=".vmode)
456 if exists("g:Align_xstrlen") && g:Align_xstrlen
457 let ragged = ( col("'>") > s:Strlen(getline("'>")) || col("'<") > s:Strlen(getline("'<")) )
459 let ragged = ( col("'>") > strlen(getline("'>")) || col("'<") > strlen(getline("'<")) )
470 " call Decho("lines[".begline.",".endline."] col[".begcol.",".endcol."] ragged=".ragged." AlignCtrl<".s:AlignCtrl.">")
474 let pastekeep= &l:paste
477 " convert selected range of lines to use spaces instead of tabs
478 " but if first line's initial white spaces are to be retained
480 if begcol <= 0 && s:AlignLeadKeep == 'I'
481 " retain first leading whitespace for all subsequent lines
482 let bgntxt= substitute(getline(begline),'^\(\s*\).\{-}$','\1','')
483 " call Decho("retaining 1st leading whitespace: bgntxt<".bgntxt.">")
486 exe begline.",".endline."ret"
489 " First pass: collect alignment data (max field sizes)
490 " Second pass: perform alignment
494 " call Decho("---- Pass ".pass.": ----")
497 while line <= endline
499 let txt = getline(line)
501 " call Decho("Pass".pass.": Line ".line." <".txt.">")
503 " AlignGPat support: allows a selector pattern (akin to g/selector/cmd )
504 if exists("s:AlignGPat")
505 " call Decho("Pass".pass.": AlignGPat<".s:AlignGPat.">")
506 if match(txt,s:AlignGPat) == -1
507 " call Decho("Pass".pass.": skipping")
513 " AlignVPat support: allows a selector pattern (akin to v/selector/cmd )
514 if exists("s:AlignVPat")
515 " call Decho("Pass".pass.": AlignVPat<".s:AlignVPat.">")
516 if match(txt,s:AlignVPat) != -1
517 " call Decho("Pass".pass.": skipping")
523 " Always skip blank lines
524 if match(txt,'^\s*$') != -1
525 " call Decho("Pass".pass.": skipping")
530 " Extract visual-block selected text (init bgntxt, endtxt)
531 if exists("g:Align_xstrlen") && g:Align_xstrlen
532 let txtlen= s:Strlen(txt)
534 let txtlen= strlen(txt)
537 " Record text to left of selected area
538 let bgntxt= strpart(txt,0,begcol)
539 " call Decho("Pass".pass.": record text to left: bgntxt<".bgntxt.">")
540 elseif s:AlignLeadKeep == 'W'
541 let bgntxt= substitute(txt,'^\(\s*\).\{-}$','\1','')
542 " call Decho("Pass".pass.": retaining all leading ws: bgntxt<".bgntxt.">")
543 elseif s:AlignLeadKeep == 'w' || !exists("bgntxt")
546 " call Decho("Pass".pass.": no beginning text")
551 " Elide any text lying outside selected columnar region
552 let endtxt= strpart(txt,endcol+1,txtlen-endcol)
553 let txt = strpart(txt,begcol,endcol-begcol+1)
556 " call Decho("Pass".pass.": bgntxt<".bgntxt.">")
557 " call Decho("Pass".pass.": txt<". txt .">")
558 " call Decho("Pass".pass.": endtxt<".endtxt.">")
559 if !exists("s:AlignPat_{1}")
560 echohl Error|echo "no separators specified!"|echohl None
561 " call Dret("Align#Align")
565 " Initialize for both passes
566 let seppat = s:AlignPat_{1}
571 let alignstyle = s:AlignStyle
574 let alignprepad = s:AlignPrePad
575 let alignpostpad= s:AlignPostPad
576 let alignsep = s:AlignSep
577 let alignophold = " "
579 " call Decho("Pass".pass.": initial alignstyle<".alignstyle."> seppat<".seppat.">")
581 " Process each field on the line
584 " C-style: cycle through pattern(s)
585 if s:AlignCtrl == 'C' && doend == 1
586 let seppat = s:AlignPat_{ipat}
587 " call Decho("Pass".pass.": processing field: AlignCtrl=".s:AlignCtrl." ipat=".ipat." seppat<".seppat.">")
589 if ipat > s:AlignPatQty
594 " cyclic alignment/justification operator handling
595 let alignophold = alignop
596 let alignop = strpart(alignstyle,0,1)
597 if alignop == '+' || doend == 2
598 let alignop= alignophold
600 let alignstyle = strpart(alignstyle,1).strpart(alignstyle,0,1)
601 let alignopnxt = strpart(alignstyle,0,1)
605 " call Decho("Pass".pass.": alignop<:> case: setting seppat<$> doend==2")
609 " cylic separator alignment specification handling
610 let alignsepop= strpart(alignsep,0,1)
611 let alignsep = strpart(alignsep,1).alignsepop
613 " mark end-of-field and the subsequent end-of-separator.
614 " Extend field if alignop is '-'
615 let endfield = match(txt,seppat,bgnfield)
616 let sepfield = matchend(txt,seppat,bgnfield)
617 let skipfield= sepfield
618 " call Decho("Pass".pass.": endfield=match(txt<".txt.">,seppat<".seppat.">,bgnfield=".bgnfield.")=".endfield)
619 while alignop == '-' && endfield != -1
620 let endfield = match(txt,seppat,skipfield)
621 let sepfield = matchend(txt,seppat,skipfield)
622 let skipfield = sepfield
623 let alignop = strpart(alignstyle,0,1)
624 let alignstyle= strpart(alignstyle,1).strpart(alignstyle,0,1)
625 " call Decho("Pass".pass.": extend field: endfield<".strpart(txt,bgnfield,endfield-bgnfield)."> alignop<".alignop."> alignstyle<".alignstyle.">")
627 let seplen= sepfield - endfield
628 " call Decho("Pass".pass.": seplen=[sepfield=".sepfield."] - [endfield=".endfield."]=".seplen)
632 " ---------------------------------------------------------------------
633 " Pass 1: Update FieldSize to max
634 " call Decho("Pass".pass.": before lead/trail remove: field<".strpart(txt,bgnfield,endfield-bgnfield).">")
635 let field = substitute(strpart(txt,bgnfield,endfield-bgnfield),'^\s*\(.\{-}\)\s*$','\1','')
636 if s:AlignLeadKeep == 'W'
637 let field = bgntxt.field
640 if exists("g:Align_xstrlen") && g:Align_xstrlen
641 let fieldlen = s:Strlen(field)
643 let fieldlen = strlen(field)
645 let sFieldSize = "FieldSize_".ifield
646 if !exists(sFieldSize)
647 let FieldSize_{ifield}= fieldlen
648 " call Decho("Pass".pass.": set FieldSize_{".ifield."}=".FieldSize_{ifield}." <".field.">")
649 elseif fieldlen > FieldSize_{ifield}
650 let FieldSize_{ifield}= fieldlen
651 " call Decho("Pass".pass.": oset FieldSize_{".ifield."}=".FieldSize_{ifield}." <".field.">")
653 let sSepSize= "SepSize_".ifield
655 let SepSize_{ifield}= seplen
656 " call Decho(" set SepSize_{".ifield."}=".SepSize_{ifield}." <".field.">")
657 elseif seplen > SepSize_{ifield}
658 let SepSize_{ifield}= seplen
659 " call Decho("Pass".pass.": oset SepSize_{".ifield."}=".SepSize_{ifield}." <".field.">")
663 " ---------------------------------------------------------------------
664 " Pass 2: Perform Alignment
665 let prepad = strpart(alignprepad,0,1)
666 let postpad = strpart(alignpostpad,0,1)
667 let alignprepad = strpart(alignprepad,1).strpart(alignprepad,0,1)
668 let alignpostpad = strpart(alignpostpad,1).strpart(alignpostpad,0,1)
669 let field = substitute(strpart(txt,bgnfield,endfield-bgnfield),'^\s*\(.\{-}\)\s*$','\1','')
670 if s:AlignLeadKeep == 'W'
671 let field = bgntxt.field
678 if exists("g:Align_xstrlen") && g:Align_xstrlen
679 let fieldlen = s:Strlen(field)
681 let fieldlen = strlen(field)
683 let sep = s:MakeSpace(prepad).strpart(txt,endfield,sepfield-endfield).s:MakeSpace(postpad)
684 if seplen < SepSize_{ifield}
686 " left-justify separators
687 let sep = sep.s:MakeSpace(SepSize_{ifield}-seplen)
688 elseif alignsepop == ">"
689 " right-justify separators
690 let sep = s:MakeSpace(SepSize_{ifield}-seplen).sep
692 " center-justify separators
693 let sepleft = (SepSize_{ifield} - seplen)/2
694 let sepright = SepSize_{ifield} - seplen - sepleft
695 let sep = s:MakeSpace(sepleft).sep.s:MakeSpace(sepright)
698 let spaces = FieldSize_{ifield} - fieldlen
699 " call Decho("Pass".pass.": Field #".ifield."<".field."> spaces=".spaces." be[".bgnfield.",".endfield."] pad=".prepad.','.postpad." FS_".ifield."<".FieldSize_{ifield}."> sep<".sep."> ragged=".ragged." doend=".doend." alignop<".alignop.">")
701 " Perform alignment according to alignment style justification
705 let spaceleft = spaces/2
706 let spaceright= FieldSize_{ifield} - spaceleft - fieldlen
707 let newtxt = newtxt.s:MakeSpace(spaceleft).field.s:MakeSpace(spaceright).sep
708 elseif alignop == 'r'
709 " right justify the field
710 let newtxt= newtxt.s:MakeSpace(spaces).field.sep
711 elseif ragged && doend == 2
712 " left justify rightmost field (no trailing blanks needed)
713 let newtxt= newtxt.field
715 " left justfiy the field
716 let newtxt= newtxt.field.s:MakeSpace(spaces).sep
718 elseif ragged && doend == 2
719 " field at maximum field size and no trailing blanks needed
720 let newtxt= newtxt.field
722 " field is at maximum field size already
723 let newtxt= newtxt.field.sep
725 " call Decho("Pass".pass.": newtxt<".newtxt.">")
728 " bgnfield indexes to end of separator at right of current field
729 " Update field counter
730 let bgnfield= sepfield
731 let ifield = ifield + 1
735 " handle end-of-text as end-of-field
741 endif " endfield != -1
742 endwhile " doend loop (as well as regularly separated fields)
745 " Write altered line to buffer
746 " call Decho("Pass".pass.": bgntxt<".bgntxt."> line=".line)
747 " call Decho("Pass".pass.": newtxt<".newtxt.">")
748 " call Decho("Pass".pass.": endtxt<".endtxt.">")
749 call setline(line,bgntxt.newtxt.endtxt)
757 " call Decho("end of two pass loop")
759 " Restore user options
761 let &l:paste = pastekeep
763 if exists("s:DoAlignPop")
764 " AlignCtrl Map support
765 call Align#AlignPop()
769 " restore current search pattern
772 let &report = keep_report
774 " call Dret("Align#Align")
778 " ---------------------------------------------------------------------
779 " Align#AlignPush: this command/function pushes an alignment control string onto a stack {{{1
780 fun! Align#AlignPush()
781 " call Dfunc("AlignPush()")
783 " initialize the stack
784 if !exists("s:AlignCtrlStackQty")
785 let s:AlignCtrlStackQty= 1
787 let s:AlignCtrlStackQty= s:AlignCtrlStackQty + 1
790 " construct an AlignCtrlStack entry
791 if !exists("s:AlignSep")
794 let s:AlignCtrlStack_{s:AlignCtrlStackQty}= s:AlignCtrl.'p'.s:AlignPrePad.'P'.s:AlignPostPad.s:AlignLeadKeep.s:AlignStyle.s:AlignSep
795 " call Decho("AlignPush: AlignCtrlStack_".s:AlignCtrlStackQty."<".s:AlignCtrlStack_{s:AlignCtrlStackQty}.">")
797 " push [GV] patterns onto their own stack
798 if exists("s:AlignGPat")
799 let s:AlignGPat_{s:AlignCtrlStackQty}= s:AlignGPat
801 let s:AlignGPat_{s:AlignCtrlStackQty}= ""
803 if exists("s:AlignVPat")
804 let s:AlignVPat_{s:AlignCtrlStackQty}= s:AlignVPat
806 let s:AlignVPat_{s:AlignCtrlStackQty}= ""
809 " call Dret("AlignPush")
812 " ---------------------------------------------------------------------
813 " Align#AlignPop: this command/function pops an alignment pattern from a stack {{{1
814 " and into the AlignCtrl variables.
815 fun! Align#AlignPop()
816 " call Dfunc("Align#AlignPop()")
819 if !exists("s:AlignCtrlStackQty")
820 echoerr "AlignPush needs to be used prior to AlignPop"
821 " call Dret("Align#AlignPop <> : AlignPush needs to have been called first")
824 if s:AlignCtrlStackQty <= 0
825 unlet s:AlignCtrlStackQty
826 echoerr "AlignPush needs to be used prior to AlignPop"
827 " call Dret("Align#AlignPop <> : AlignPop needs to have been called first")
831 " pop top of AlignCtrlStack and pass value to AlignCtrl
832 let retval=s:AlignCtrlStack_{s:AlignCtrlStackQty}
833 unlet s:AlignCtrlStack_{s:AlignCtrlStackQty}
834 call Align#AlignCtrl(retval)
836 " pop G pattern stack
837 if s:AlignGPat_{s:AlignCtrlStackQty} != ""
838 call Align#AlignCtrl('g',s:AlignGPat_{s:AlignCtrlStackQty})
840 call Align#AlignCtrl('g')
842 unlet s:AlignGPat_{s:AlignCtrlStackQty}
844 " pop V pattern stack
845 if s:AlignVPat_{s:AlignCtrlStackQty} != ""
846 call Align#AlignCtrl('v',s:AlignVPat_{s:AlignCtrlStackQty})
848 call Align#AlignCtrl('v')
851 unlet s:AlignVPat_{s:AlignCtrlStackQty}
852 let s:AlignCtrlStackQty= s:AlignCtrlStackQty - 1
854 " call Dret("Align#AlignPop <".retval."> : AlignCtrlStackQty=".s:AlignCtrlStackQty)
858 " ---------------------------------------------------------------------
859 " Align#AlignReplaceQuotedSpaces: {{{1
860 fun! Align#AlignReplaceQuotedSpaces()
861 " call Dfunc("AlignReplaceQuotedSpaces()")
863 let l:line = getline(line("."))
864 if exists("g:Align_xstrlen") && g:Align_xstrlen
865 let l:linelen = s:Strlen(l:line)
867 let l:linelen = strlen(l:line)
869 let l:startingPos = 0
870 let l:startQuotePos = 0
871 let l:endQuotePos = 0
873 let l:quoteRe = '\\\@<!"'
875 " "call Decho("in replace spaces. line=" . line('.'))
877 let l:startQuotePos = match(l:line, l:quoteRe, l:startingPos)
878 if (l:startQuotePos < 0)
879 " "call Decho("No more quotes to the end of line")
882 let l:endQuotePos = match(l:line, l:quoteRe, l:startQuotePos + 1)
883 if (l:endQuotePos < 0)
884 " "call Decho("Mismatched quotes")
887 let l:spaceReplaceRe = '^.\{' . (l:startQuotePos + 1) . '}.\{-}\zs\s\ze.*.\{' . (linelen - l:endQuotePos) . '}$'
888 " "call Decho('spaceReplaceRe="' . l:spaceReplaceRe . '"')
889 let l:newStr = substitute(l:line, l:spaceReplaceRe, '%', '')
890 while (l:newStr != l:line)
891 " "call Decho('newstr="' . l:newStr . '"')
892 let l:line = l:newStr
893 let l:newStr = substitute(l:line, l:spaceReplaceRe, '%', '')
895 let l:startingPos = l:endQuotePos + 1
897 call setline(line('.'), l:line)
899 " call Dret("AlignReplaceQuotedSpaces")
902 " ---------------------------------------------------------------------
903 " s:QArgSplitter: to avoid \ processing by <f-args>, <q-args> is needed. {{{1
904 " However, <q-args> doesn't split at all, so this function returns a list
905 " of arguments which has been:
906 " * split at whitespace
907 " * unless inside "..."s. One may escape characters with a backslash inside double quotes.
908 " along with a leading length-of-list.
910 " Examples: %Align "\"" will align on "s
911 " %Align " " will align on spaces
913 " The resulting list: qarglist[0] corresponds to a:0
914 " qarglist[i] corresponds to a:{i}
915 fun! s:QArgSplitter(qarg)
916 " call Dfunc("s:QArgSplitter(qarg<".a:qarg.">)")
919 " handle "..." args, which may include whitespace
922 " call Decho("handle quoted arguments: args<".args.">")
925 let arglen = strlen(args)
926 " call Decho("args[".iarg."]<".args[iarg]."> arglen=".arglen)
927 " find index to first not-escaped '"'
928 while args[iarg] != '"' && iarg < arglen
930 let args= strpart(args,1)
934 " call Decho("args<".args."> iarg=".iarg." arglen=".arglen)
937 " handle left of quote or remaining section
938 " call Decho("handle left of quote or remaining section")
940 let qarglist= qarglist + split(strpart(args,0,iarg-1))
942 let qarglist= qarglist + split(strpart(args,0,iarg))
944 let args = strpart(args,iarg)
945 let arglen = strlen(args)
947 elseif iarg < arglen && args[0] == '"'
948 " handle "quoted" section
949 " call Decho("handle quoted section")
951 while args[iarg] != '"' && iarg < arglen
953 let args= strpart(args,1)
957 " call Decho("args<".args."> iarg=".iarg." arglen=".arglen)
959 call add(qarglist,strpart(args,1,iarg-1))
960 let args= strpart(args,iarg+1)
962 let qarglist = qarglist + split(args)
966 " call Decho("qarglist".string(qarglist)." iarg=".iarg." args<".args.">")
970 " split at all whitespace
971 let qarglist= split(a:qarg)
974 let qarglistlen= len(qarglist)
975 let qarglist = insert(qarglist,qarglistlen)
976 " call Dret("s:QArgSplitter ".string(qarglist))
980 " ---------------------------------------------------------------------
981 " s:Strlen: this function returns the length of a string, even if its {{{1
982 " using two-byte etc characters.
983 " Currently, its only used if g:Align_xstrlen is set to a
984 " nonzero value. Solution from Nicolai Weibull, vim docs
985 " (:help strlen()), Tony Mechelynck, and my own invention.
987 " call Dfunc("s:Strlen(x<".a:x.">")
988 if g:Align_xstrlen == 1
989 " number of codepoints (Latin a + combining circumflex is two codepoints)
990 " (comment from TM, solution from NW)
991 let ret= strlen(substitute(a:x,'.','c','g'))
993 elseif g:Align_xstrlen == 2
994 " number of spacing codepoints (Latin a + combining circumflex is one spacing
995 " codepoint; a hard tab is one; wide and narrow CJK are one each; etc.)
996 " (comment from TM, solution from TM)
997 let ret=strlen(substitute(a:x, '.\Z', 'x', 'g'))
999 elseif g:Align_xstrlen == 3
1000 " virtual length (counting, for instance, tabs as anything between 1 and
1001 " 'tabstop', wide CJK as 2 rather than 1, Arabic alif as zero when immediately
1002 " preceded by lam, one otherwise, etc.)
1003 " (comment from TM, solution from me)
1006 call setline(line("."),a:x)
1007 let ret= virtcol("$") - 1
1012 " at least give a decent default
1015 " call Dret("s:Strlen ".ret)
1019 " ---------------------------------------------------------------------
1020 " Set up default values: {{{1
1021 "call Decho("-- Begin AlignCtrl Initialization --")
1022 call Align#AlignCtrl("default")
1023 "call Decho("-- End AlignCtrl Initialization --")
1025 " ---------------------------------------------------------------------
1029 " vim: ts=4 fdm=marker