]> git.r.bdr.sh - rbdr/dotfiles/blob - vim/autoload/Align.vim
bce35428d2c0a8ab0b939df53f5b6f9bc6b74dc8
[rbdr/dotfiles] / vim / autoload / Align.vim
1 " Align: tool to align multiple fields based on one or more separators
2 " Author: Charles E. Campbell, Jr.
3 " Date: Mar 03, 2009
4 " Version: 35
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
15 " of this software.
16 "
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
20 " faith to faith.
21
22 " ---------------------------------------------------------------------
23 " Load Once: {{{1
24 if exists("g:loaded_Align") || &cp
25 finish
26 endif
27 let g:loaded_Align = "v35"
28 if v:version < 700
29 echohl WarningMsg
30 echo "***warning*** this version of Align needs vim 7.0"
31 echohl Normal
32 finish
33 endif
34 let s:keepcpo= &cpo
35 set cpo&vim
36 "DechoTabOn
37
38 " ---------------------------------------------------------------------
39 " Debugging Support: {{{1
40 "if !exists("g:loaded_Decho") | runtime plugin/Decho.vim | endif
41
42 " ---------------------------------------------------------------------
43 " Options: {{{1
44 if !exists("g:Align_xstrlen")
45 if &enc == "latin1" || $LANG == "en_US.UTF-8" || !has("multi_byte")
46 let g:Align_xstrlen= 0
47 else
48 let g:Align_xstrlen= 1
49 endif
50 endif
51
52 " ---------------------------------------------------------------------
53 " Align#AlignCtrl: enter alignment patterns here {{{1
54 "
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
59 " c center 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
65 " | center separators
66 "
67 " Builds = s:AlignPat s:AlignCtrl s:AlignPatQty
68 " C s:AlignPat s:AlignCtrl s:AlignPatQty
69 " p s:AlignPrePad
70 " P s:AlignPostPad
71 " w s:AlignLeadKeep
72 " W s:AlignLeadKeep
73 " I s:AlignLeadKeep
74 " l s:AlignStyle
75 " r s:AlignStyle
76 " - s:AlignStyle
77 " + s:AlignStyle
78 " : s:AlignStyle
79 " c s:AlignStyle
80 " g s:AlignGPat
81 " v s:AlignVPat
82 " < s:AlignSep
83 " > s:AlignSep
84 " | s:AlignSep
85 fun! Align#AlignCtrl(...)
86
87 " call Dfunc("AlignCtrl(...) a:0=".a:0)
88
89 " save options that will be changed
90 let keep_search = @/
91 let keep_ic = &ic
92
93 " turn ignorecase off
94 set noic
95
96 " clear visual mode so that old visual-mode selections don't
97 " get applied to new invocations of Align().
98 if v:version < 602
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"
102 endif
103 elseif exists("s:dovisclear")
104 " call Decho("clearing visual mode a:0=".a:0." a:1<".a:1.">")
105 let clearvmode= visualmode(1)
106 endif
107
108 " set up a list akin to an argument list
109 if a:0 > 0
110 let A= s:QArgSplitter(a:1)
111 else
112 let A=[0]
113 endif
114
115 if A[0] > 0
116 let style = A[1]
117
118 " Check for bad separator patterns (zero-length matches)
119 " (but zero-length patterns for g/v is ok)
120 if style !~# '[gv]'
121 let ipat= 2
122 while ipat <= A[0]
123 if "" =~ A[ipat]
124 echoerr "AlignCtrl: separator<".A[ipat]."> matches zero-length string"
125 let &ic= keep_ic
126 " call Dret("AlignCtrl")
127 return
128 endif
129 let ipat= ipat + 1
130 endwhile
131 endif
132 endif
133
134 " call Decho("AlignCtrl() A[0]=".A[0])
135 if !exists("s:AlignStyle")
136 let s:AlignStyle= "l"
137 endif
138 if !exists("s:AlignPrePad")
139 let s:AlignPrePad= 0
140 endif
141 if !exists("s:AlignPostPad")
142 let s:AlignPostPad= 0
143 endif
144 if !exists("s:AlignLeadKeep")
145 let s:AlignLeadKeep= 'w'
146 endif
147
148 if A[0] == 0
149 " ----------------------
150 " List current selection
151 " ----------------------
152 if !exists("s:AlignPatQty")
153 let s:AlignPatQty= 0
154 endif
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.">"
163 endif
164 let ipat= 1
165 while ipat <= s:AlignPatQty
166 echo "Pat".ipat."<".s:AlignPat_{ipat}.">"
167 " call Decho("Pat".ipat."<".s:AlignPat_{ipat}.">")
168 let ipat= ipat + 1
169 endwhile
170
171 else
172 " ----------------------------------
173 " Process alignment control settings
174 " ----------------------------------
175 " call Decho("process the alignctrl settings")
176 " call Decho("style<".style.">")
177
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()
185 endwhile
186 unlet s:AlignCtrlStackQty
187 endif
188 " Set AlignCtrl to its default value
189 call Align#AlignCtrl("Ilp1P1=<",'=')
190 call Align#AlignCtrl("g")
191 call Align#AlignCtrl("v")
192 let s:dovisclear = 1
193 let &ic = keep_ic
194 let @/ = keep_search
195 " call Dret("AlignCtrl")
196 return
197 endif
198
199 if style =~# 'm'
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()
204 let s:DoAlignPop= 1
205 endif
206
207 " = : record a list of alignment patterns that are equivalent
208 if style =~# "="
209 " call Decho("style case =: record list of equiv alignment patterns")
210 let s:AlignCtrl = '='
211 if A[0] >= 2
212 let s:AlignPatQty= 1
213 let s:AlignPat_1 = A[2]
214 let ipat = 3
215 while ipat <= A[0]
216 let s:AlignPat_1 = s:AlignPat_1.'\|'.A[ipat]
217 let ipat = ipat + 1
218 endwhile
219 let s:AlignPat_1= '\('.s:AlignPat_1.'\)'
220 " call Decho("AlignCtrl<".s:AlignCtrl."> AlignPat<".s:AlignPat_1.">")
221 endif
222
223 "c : cycle through alignment pattern(s)
224 elseif style =~# 'C'
225 " call Decho("style case C: cycle through alignment pattern(s)")
226 let s:AlignCtrl = 'C'
227 if A[0] >= 2
228 let s:AlignPatQty= A[0] - 1
229 let ipat = 1
230 while ipat < A[0]
231 let s:AlignPat_{ipat}= A[ipat+1]
232 " call Decho("AlignCtrl<".s:AlignCtrl."> AlignQty=".s:AlignPatQty." AlignPat_".ipat."<".s:AlignPat_{ipat}.">")
233 let ipat= ipat + 1
234 endwhile
235 endif
236 endif
237
238 if style =~# 'p'
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'
243 let @/ = keep_search
244 let &ic= keep_ic
245 " call Dret("AlignCtrl")
246 return
247 endif
248 endif
249
250 if style =~# 'P'
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'
255 let @/ = keep_search
256 let &ic= keep_ic
257 " call Dret("AlignCtrl")
258 return
259 endif
260 endif
261
262 if style =~# 'w'
263 " call Decho("style case w: ignore leading whitespace")
264 let s:AlignLeadKeep= 'w'
265 elseif style =~# 'W'
266 " call Decho("style case w: keep leading whitespace")
267 let s:AlignLeadKeep= 'W'
268 elseif style =~# 'I'
269 " call Decho("style case w: retain initial leading whitespace")
270 let s:AlignLeadKeep= 'I'
271 endif
272
273 if style =~# 'g'
274 " first list item is a "g" selector pattern
275 " call Decho("style case g: global selector pattern")
276 if A[0] < 2
277 if exists("s:AlignGPat")
278 unlet s:AlignGPat
279 " call Decho("unlet s:AlignGPat")
280 endif
281 else
282 let s:AlignGPat= A[2]
283 " call Decho("s:AlignGPat<".s:AlignGPat.">")
284 endif
285 elseif style =~# 'v'
286 " first list item is a "v" selector pattern
287 " call Decho("style case v: global selector anti-pattern")
288 if A[0] < 2
289 if exists("s:AlignVPat")
290 unlet s:AlignVPat
291 " call Decho("unlet s:AlignVPat")
292 endif
293 else
294 let s:AlignVPat= A[2]
295 " call Decho("s:AlignVPat<".s:AlignVPat.">")
296 endif
297 endif
298
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.">")
304 endif
305
306 "[<>|] : set up s:AlignSep
307 if style =~# '[<>|]'
308 " call Decho("style case [-lrc+:]: separator justification")
309 let s:AlignSep= substitute(style,'[^<>|]','','g')
310 " call Decho("AlignSep ".s:AlignSep)
311 endif
312 endif
313
314 " sanity
315 if !exists("s:AlignCtrl")
316 let s:AlignCtrl= '='
317 endif
318
319 " restore search and options
320 let @/ = keep_search
321 let &ic= keep_ic
322
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
325 endfun
326
327 " ---------------------------------------------------------------------
328 " s:MakeSpace: returns a string with spacecnt blanks {{{1
329 fun! s:MakeSpace(spacecnt)
330 " call Dfunc("MakeSpace(spacecnt=".a:spacecnt.")")
331 let str = ""
332 let spacecnt = a:spacecnt
333 while spacecnt > 0
334 let str = str . " "
335 let spacecnt = spacecnt - 1
336 endwhile
337 " call Dret("MakeSpace <".str.">")
338 return str
339 endfun
340
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)
345
346 " sanity checks
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")
350 return
351 endif
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")
355 return
356 endif
357
358 " set up a list akin to an argument list
359 if a:0 > 0
360 let A= s:QArgSplitter(a:1)
361 else
362 let A=[0]
363 endif
364
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")
371 if A[1] =~ '[gv]'
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")
376 elseif A[1] !~ 'm'
377 call Align#AlignCtrl(A[1]."m")
378 else
379 call Align#AlignCtrl(A[1])
380 endif
381 endif
382
383 " Check for bad separator patterns (zero-length matches)
384 let ipat= 1 + hasctrl
385 while ipat <= A[0]
386 if "" =~ A[ipat]
387 echoerr "Align: separator<".A[ipat]."> matches zero-length string"
388 " call Dret("Align#Align")
389 return
390 endif
391 let ipat= ipat + 1
392 endwhile
393
394 " record current search pattern for subsequent restoration
395 let keep_search= @/
396 let keep_ic = &ic
397 let keep_report= &report
398 set noic report=10000
399
400 if A[0] > hasctrl
401 " Align will accept a list of separator regexps
402 " call Decho("A[0]=".A[0].": accepting list of separator regexp")
403
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]
409 let s:AlignPatQty= 1
410 let ipat = 2 + hasctrl
411 while ipat <= A[0]
412 let s:AlignPat_1 = s:AlignPat_1.'\|'.A[ipat]
413 let ipat = ipat + 1
414 endwhile
415 let s:AlignPat_1= '\('.s:AlignPat_1.'\)'
416 " call Decho("AlignCtrl<".s:AlignCtrl."> AlignPat<".s:AlignPat_1.">")
417
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
423 let ipat = 1
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}.">")
427 let ipat= ipat + 1
428 endwhile
429 endif
430 endif
431
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
439 if begcol > endcol
440 let begcol = virtcol("'>")-1
441 let endcol = virtcol("'<")-1
442 endif
443 " call Decho("begcol=".begcol." endcol=".endcol)
444 let begline = a:firstline
445 let endline = a:lastline
446 if begline > endline
447 let begline = a:lastline
448 let endline = a:firstline
449 endif
450 " call Decho("begline=".begline." endline=".endline)
451 let fieldcnt = 0
452 if (begline == line("'>") && endline == line("'<")) || (begline == line("'<") && endline == line("'>"))
453 let vmode= visualmode()
454 " call Decho("vmode=".vmode)
455 if vmode == "\<c-v>"
456 if exists("g:Align_xstrlen") && g:Align_xstrlen
457 let ragged = ( col("'>") > s:Strlen(getline("'>")) || col("'<") > s:Strlen(getline("'<")) )
458 else
459 let ragged = ( col("'>") > strlen(getline("'>")) || col("'<") > strlen(getline("'<")) )
460 endif
461 else
462 let ragged= 1
463 endif
464 else
465 let ragged= 1
466 endif
467 if ragged
468 let begcol= 0
469 endif
470 " call Decho("lines[".begline.",".endline."] col[".begcol.",".endcol."] ragged=".ragged." AlignCtrl<".s:AlignCtrl.">")
471
472 " Keep user options
473 let etkeep = &l:et
474 let pastekeep= &l:paste
475 setlocal et paste
476
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
479 " then use 'em
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.">")
484 set noet
485 endif
486 exe begline.",".endline."ret"
487
488 " Execute two passes
489 " First pass: collect alignment data (max field sizes)
490 " Second pass: perform alignment
491 let pass= 1
492 while pass <= 2
493 " call Decho(" ")
494 " call Decho("---- Pass ".pass.": ----")
495
496 let line= begline
497 while line <= endline
498 " Process each line
499 let txt = getline(line)
500 " call Decho(" ")
501 " call Decho("Pass".pass.": Line ".line." <".txt.">")
502
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")
508 let line= line + 1
509 continue
510 endif
511 endif
512
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")
518 let line= line + 1
519 continue
520 endif
521 endif
522
523 " Always skip blank lines
524 if match(txt,'^\s*$') != -1
525 " call Decho("Pass".pass.": skipping")
526 let line= line + 1
527 continue
528 endif
529
530 " Extract visual-block selected text (init bgntxt, endtxt)
531 if exists("g:Align_xstrlen") && g:Align_xstrlen
532 let txtlen= s:Strlen(txt)
533 else
534 let txtlen= strlen(txt)
535 endif
536 if begcol > 0
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")
544 " No beginning text
545 let bgntxt= ""
546 " call Decho("Pass".pass.": no beginning text")
547 endif
548 if ragged
549 let endtxt= ""
550 else
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)
554 endif
555 " call Decho(" ")
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")
562 return
563 endif
564
565 " Initialize for both passes
566 let seppat = s:AlignPat_{1}
567 let ifield = 1
568 let ipat = 1
569 let bgnfield = 0
570 let endfield = 0
571 let alignstyle = s:AlignStyle
572 let doend = 1
573 let newtxt = ""
574 let alignprepad = s:AlignPrePad
575 let alignpostpad= s:AlignPostPad
576 let alignsep = s:AlignSep
577 let alignophold = " "
578 let alignop = "l"
579 " call Decho("Pass".pass.": initial alignstyle<".alignstyle."> seppat<".seppat.">")
580
581 " Process each field on the line
582 while doend > 0
583
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.">")
588 let ipat = ipat + 1
589 if ipat > s:AlignPatQty
590 let ipat = 1
591 endif
592 endif
593
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
599 else
600 let alignstyle = strpart(alignstyle,1).strpart(alignstyle,0,1)
601 let alignopnxt = strpart(alignstyle,0,1)
602 if alignop == ':'
603 let seppat = '$'
604 let doend = 2
605 " call Decho("Pass".pass.": alignop<:> case: setting seppat<$> doend==2")
606 endif
607 endif
608
609 " cylic separator alignment specification handling
610 let alignsepop= strpart(alignsep,0,1)
611 let alignsep = strpart(alignsep,1).alignsepop
612
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.">")
626 endwhile
627 let seplen= sepfield - endfield
628 " call Decho("Pass".pass.": seplen=[sepfield=".sepfield."] - [endfield=".endfield."]=".seplen)
629
630 if endfield != -1
631 if pass == 1
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
638 let bgntxt= ""
639 endif
640 if exists("g:Align_xstrlen") && g:Align_xstrlen
641 let fieldlen = s:Strlen(field)
642 else
643 let fieldlen = strlen(field)
644 endif
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.">")
652 endif
653 let sSepSize= "SepSize_".ifield
654 if !exists(sSepSize)
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.">")
660 endif
661
662 else
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
672 let bgntxt= ""
673 endif
674 if doend == 2
675 let prepad = 0
676 let postpad= 0
677 endif
678 if exists("g:Align_xstrlen") && g:Align_xstrlen
679 let fieldlen = s:Strlen(field)
680 else
681 let fieldlen = strlen(field)
682 endif
683 let sep = s:MakeSpace(prepad).strpart(txt,endfield,sepfield-endfield).s:MakeSpace(postpad)
684 if seplen < SepSize_{ifield}
685 if alignsepop == "<"
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
691 else
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)
696 endif
697 endif
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.">")
700
701 " Perform alignment according to alignment style justification
702 if spaces > 0
703 if alignop == 'c'
704 " center the field
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
714 else
715 " left justfiy the field
716 let newtxt= newtxt.field.s:MakeSpace(spaces).sep
717 endif
718 elseif ragged && doend == 2
719 " field at maximum field size and no trailing blanks needed
720 let newtxt= newtxt.field
721 else
722 " field is at maximum field size already
723 let newtxt= newtxt.field.sep
724 endif
725 " call Decho("Pass".pass.": newtxt<".newtxt.">")
726 endif " pass 1/2
727
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
732 if doend == 2
733 let doend= 0
734 endif
735 " handle end-of-text as end-of-field
736 elseif doend == 1
737 let seppat = '$'
738 let doend = 2
739 else
740 let doend = 0
741 endif " endfield != -1
742 endwhile " doend loop (as well as regularly separated fields)
743
744 if pass == 2
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)
750 endif
751
752 let line = line + 1
753 endwhile " line loop
754
755 let pass= pass + 1
756 endwhile " pass loop
757 " call Decho("end of two pass loop")
758
759 " Restore user options
760 let &l:et = etkeep
761 let &l:paste = pastekeep
762
763 if exists("s:DoAlignPop")
764 " AlignCtrl Map support
765 call Align#AlignPop()
766 unlet s:DoAlignPop
767 endif
768
769 " restore current search pattern
770 let @/ = keep_search
771 let &ic = keep_ic
772 let &report = keep_report
773
774 " call Dret("Align#Align")
775 return
776 endfun
777
778 " ---------------------------------------------------------------------
779 " Align#AlignPush: this command/function pushes an alignment control string onto a stack {{{1
780 fun! Align#AlignPush()
781 " call Dfunc("AlignPush()")
782
783 " initialize the stack
784 if !exists("s:AlignCtrlStackQty")
785 let s:AlignCtrlStackQty= 1
786 else
787 let s:AlignCtrlStackQty= s:AlignCtrlStackQty + 1
788 endif
789
790 " construct an AlignCtrlStack entry
791 if !exists("s:AlignSep")
792 let s:AlignSep= ''
793 endif
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}.">")
796
797 " push [GV] patterns onto their own stack
798 if exists("s:AlignGPat")
799 let s:AlignGPat_{s:AlignCtrlStackQty}= s:AlignGPat
800 else
801 let s:AlignGPat_{s:AlignCtrlStackQty}= ""
802 endif
803 if exists("s:AlignVPat")
804 let s:AlignVPat_{s:AlignCtrlStackQty}= s:AlignVPat
805 else
806 let s:AlignVPat_{s:AlignCtrlStackQty}= ""
807 endif
808
809 " call Dret("AlignPush")
810 endfun
811
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()")
817
818 " sanity checks
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")
822 return ""
823 endif
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")
828 return ""
829 endif
830
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)
835
836 " pop G pattern stack
837 if s:AlignGPat_{s:AlignCtrlStackQty} != ""
838 call Align#AlignCtrl('g',s:AlignGPat_{s:AlignCtrlStackQty})
839 else
840 call Align#AlignCtrl('g')
841 endif
842 unlet s:AlignGPat_{s:AlignCtrlStackQty}
843
844 " pop V pattern stack
845 if s:AlignVPat_{s:AlignCtrlStackQty} != ""
846 call Align#AlignCtrl('v',s:AlignVPat_{s:AlignCtrlStackQty})
847 else
848 call Align#AlignCtrl('v')
849 endif
850
851 unlet s:AlignVPat_{s:AlignCtrlStackQty}
852 let s:AlignCtrlStackQty= s:AlignCtrlStackQty - 1
853
854 " call Dret("Align#AlignPop <".retval."> : AlignCtrlStackQty=".s:AlignCtrlStackQty)
855 return retval
856 endfun
857
858 " ---------------------------------------------------------------------
859 " Align#AlignReplaceQuotedSpaces: {{{1
860 fun! Align#AlignReplaceQuotedSpaces()
861 " call Dfunc("AlignReplaceQuotedSpaces()")
862
863 let l:line = getline(line("."))
864 if exists("g:Align_xstrlen") && g:Align_xstrlen
865 let l:linelen = s:Strlen(l:line)
866 else
867 let l:linelen = strlen(l:line)
868 endif
869 let l:startingPos = 0
870 let l:startQuotePos = 0
871 let l:endQuotePos = 0
872 let l:spacePos = 0
873 let l:quoteRe = '\\\@<!"'
874
875 " "call Decho("in replace spaces. line=" . line('.'))
876 while (1)
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")
880 break
881 endif
882 let l:endQuotePos = match(l:line, l:quoteRe, l:startQuotePos + 1)
883 if (l:endQuotePos < 0)
884 " "call Decho("Mismatched quotes")
885 break
886 endif
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, '%', '')
894 endwhile
895 let l:startingPos = l:endQuotePos + 1
896 endwhile
897 call setline(line('.'), l:line)
898
899 " call Dret("AlignReplaceQuotedSpaces")
900 endfun
901
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.
909 "
910 " Examples: %Align "\"" will align on "s
911 " %Align " " will align on spaces
912 "
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.">)")
917
918 if a:qarg =~ '".*"'
919 " handle "..." args, which may include whitespace
920 let qarglist = []
921 let args = a:qarg
922 " call Decho("handle quoted arguments: args<".args.">")
923 while args != ""
924 let iarg = 0
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
929 if args[iarg] == '\'
930 let args= strpart(args,1)
931 endif
932 let iarg= iarg + 1
933 endwhile
934 " call Decho("args<".args."> iarg=".iarg." arglen=".arglen)
935
936 if iarg > 0
937 " handle left of quote or remaining section
938 " call Decho("handle left of quote or remaining section")
939 if args[iarg] == '"'
940 let qarglist= qarglist + split(strpart(args,0,iarg-1))
941 else
942 let qarglist= qarglist + split(strpart(args,0,iarg))
943 endif
944 let args = strpart(args,iarg)
945 let arglen = strlen(args)
946
947 elseif iarg < arglen && args[0] == '"'
948 " handle "quoted" section
949 " call Decho("handle quoted section")
950 let iarg= 1
951 while args[iarg] != '"' && iarg < arglen
952 if args[iarg] == '\'
953 let args= strpart(args,1)
954 endif
955 let iarg= iarg + 1
956 endwhile
957 " call Decho("args<".args."> iarg=".iarg." arglen=".arglen)
958 if args[iarg] == '"'
959 call add(qarglist,strpart(args,1,iarg-1))
960 let args= strpart(args,iarg+1)
961 else
962 let qarglist = qarglist + split(args)
963 let args = ""
964 endif
965 endif
966 " call Decho("qarglist".string(qarglist)." iarg=".iarg." args<".args.">")
967 endwhile
968
969 else
970 " split at all whitespace
971 let qarglist= split(a:qarg)
972 endif
973
974 let qarglistlen= len(qarglist)
975 let qarglist = insert(qarglist,qarglistlen)
976 " call Dret("s:QArgSplitter ".string(qarglist))
977 return qarglist
978 endfun
979
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.
986 fun! s:Strlen(x)
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'))
992
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'))
998
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)
1004 let modkeep= &l:mod
1005 exe "norm! o\<esc>"
1006 call setline(line("."),a:x)
1007 let ret= virtcol("$") - 1
1008 d
1009 let &l:mod= modkeep
1010
1011 else
1012 " at least give a decent default
1013 ret= strlen(a:x)
1014 endif
1015 " call Dret("s:Strlen ".ret)
1016 return ret
1017 endfun
1018
1019 " ---------------------------------------------------------------------
1020 " Set up default values: {{{1
1021 "call Decho("-- Begin AlignCtrl Initialization --")
1022 call Align#AlignCtrl("default")
1023 "call Decho("-- End AlignCtrl Initialization --")
1024
1025 " ---------------------------------------------------------------------
1026 " Restore: {{{1
1027 let &cpo= s:keepcpo
1028 unlet s:keepcpo
1029 " vim: ts=4 fdm=marker