Add vim plugin tabular
dblume

dblume commited on 2024-10-26 01:08:07
Showing 13 changed files, with 2368 additions and 1 deletions.


See https://github.com/godlygeek/tabular
This common command will usually do what I need:

    :Tab /|
... ...
@@ -0,0 +1,24 @@
1
+Copyright (c) 2016, Matthew J. Wozniski
2
+All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without
5
+modification, are permitted provided that the following conditions are met:
6
+    * Redistributions of source code must retain the above copyright notice,
7
+      this list of conditions and the following disclaimer.
8
+    * Redistributions in binary form must reproduce the above copyright
9
+      notice, this list of conditions and the following disclaimer in the
10
+      documentation and/or other materials provided with the distribution.
11
+    * The names of the contributors may not be used to endorse or promote
12
+      products derived from this software without specific prior written
13
+      permission.
14
+
15
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
16
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
18
+NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
19
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
21
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
24
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
... ...
@@ -0,0 +1,38 @@
1
+Tabular
2
+==============
3
+Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
4
+computer do this for you, since aligning things by hand quickly becomes
5
+unpleasant.  While there are other plugins for aligning text, the ones I've
6
+tried are either impossibly difficult to understand and use, or too simplistic
7
+to handle complicated tasks.  This plugin aims to make the easy things easy
8
+and the hard things possible, without providing an unnecessarily obtuse
9
+interface.  It's still a work in progress, and criticisms are welcome.
10
+
11
+See [Aligning Text with Tabular.vim](http://vimcasts.org/episodes/aligning-text-with-tabular-vim/)
12
+for a screencast that shows how Tabular.vim works.
13
+
14
+See [doc/Tabular.txt](http://raw.github.com/godlygeek/tabular/master/doc/Tabular.txt)
15
+for detailed documentation.
16
+
17
+Installation
18
+==============
19
+
20
+## Vim 8.1+
21
+
22
+No third-party package manager is required! Clone into:
23
+
24
+`.vim/pack/plugins/start`
25
+
26
+Make sure you include `packloadall` in your `.vimrc`.
27
+
28
+## Pathogen
29
+
30
+    mkdir -p ~/.vim/bundle
31
+    cd ~/.vim/bundle
32
+    git clone https://github.com/godlygeek/tabular.git
33
+
34
+Once help tags have been generated (either using Pathogen's `:Helptags`
35
+command, or by pointing vim's `:helptags` command at the directory where you
36
+installed Tabular), you can view the manual with `:help tabular`.
37
+
38
+See [pathogen.vim](https://github.com/tpope/vim-pathogen) for help or for package manager installation.
... ...
@@ -0,0 +1,74 @@
1
+" Copyright (c) 2016, Matthew J. Wozniski
2
+" All rights reserved.
3
+"
4
+" Redistribution and use in source and binary forms, with or without
5
+" modification, are permitted provided that the following conditions are met:
6
+"     * Redistributions of source code must retain the above copyright notice,
7
+"       this list of conditions and the following disclaimer.
8
+"     * Redistributions in binary form must reproduce the above copyright
9
+"       notice, this list of conditions and the following disclaimer in the
10
+"       documentation and/or other materials provided with the distribution.
11
+"     * The names of the contributors may not be used to endorse or promote
12
+"       products derived from this software without specific prior written
13
+"       permission.
14
+"
15
+" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
16
+" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
+" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
18
+" NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
19
+" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
+" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
21
+" OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22
+" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23
+" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
24
+" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
26
+if !exists(':Tabularize') || get(g:, 'no_default_tabular_maps', 0)
27
+  finish " Tabular.vim wasn't loaded or the default maps are unwanted
28
+endif
29
+
30
+let s:save_cpo = &cpo
31
+set cpo&vim
32
+
33
+AddTabularPattern!  assignment      /[|&+*/%<>=!~-]\@<!\([<>!=]=\|=\~\)\@![|&+*/%<>=!~-]*=/l1r1
34
+AddTabularPattern!  two_spaces      /  /l0
35
+
36
+AddTabularPipeline! multiple_spaces /  / map(a:lines, "substitute(v:val, '   *', '  ', 'g')") | tabular#TabularizeStrings(a:lines, '  ', 'l0')
37
+AddTabularPipeline! spaces          / /  map(a:lines, "substitute(v:val, '  *' , ' ' , 'g')") | tabular#TabularizeStrings(a:lines, ' ' , 'l0')
38
+
39
+AddTabularPipeline! argument_list   /(.*)/ map(a:lines, 'substitute(v:val, ''\s*\([(,)]\)\s*'', ''\1'', ''g'')')
40
+                                       \ | tabular#TabularizeStrings(a:lines, '[(,)]', 'l0')
41
+                                       \ | map(a:lines, 'substitute(v:val, ''\(\s*\),'', '',\1 '', "g")')
42
+                                       \ | map(a:lines, 'substitute(v:val, ''\s*)'', ")", "g")')
43
+
44
+function! SplitCDeclarations(lines)
45
+  let rv = []
46
+  for line in a:lines
47
+    " split the line into declaractions
48
+    let split = split(line, '\s*[,;]\s*')
49
+    " separate the type from the first declaration
50
+    let type = substitute(split[0], '\%(\%([&*]\s*\)*\)\=\k\+$', '', '')
51
+    " add the ; back on every declaration
52
+    call map(split, 'v:val . ";"')
53
+    " add the first element to the return as-is, and remove it from the list
54
+    let rv += [ remove(split, 0) ]
55
+    " transform the other elements by adding the type on at the beginning
56
+    call map(split, 'type . v:val')
57
+    " and add them all to the return
58
+    let rv += split
59
+  endfor
60
+  return rv
61
+endfunction
62
+
63
+AddTabularPipeline! split_declarations /,.*;/ SplitCDeclarations(a:lines)
64
+
65
+AddTabularPattern! ternary_operator /^.\{-}\zs?\|:/l1
66
+
67
+AddTabularPattern! cpp_io /<<\|>>/l1
68
+
69
+AddTabularPattern! pascal_assign /:=/l1
70
+
71
+AddTabularPattern! trailing_c_comments /\/\*\|\*\/\|\/\//l1
72
+
73
+let &cpo = s:save_cpo
74
+unlet s:save_cpo
... ...
@@ -0,0 +1,437 @@
1
+" Tabular:     Align columnar data using regex-designated column boundaries
2
+" Maintainer:  Matthew Wozniski (godlygeek@gmail.com)
3
+" Date:        Thu, 03 May 2012 20:49:32 -0400
4
+" Version:     1.0
5
+"
6
+" Long Description:
7
+" Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
8
+" computer do this for you, since aligning things by hand quickly becomes
9
+" unpleasant.  While there are other plugins for aligning text, the ones I've
10
+" tried are either impossibly difficult to understand and use, or too simplistic
11
+" to handle complicated tasks.  This plugin aims to make the easy things easy
12
+" and the hard things possible, without providing an unnecessarily obtuse
13
+" interface.  It's still a work in progress, and criticisms are welcome.
14
+"
15
+" License:
16
+" Copyright (c) 2012, Matthew J. Wozniski
17
+" All rights reserved.
18
+"
19
+" Redistribution and use in source and binary forms, with or without
20
+" modification, are permitted provided that the following conditions are met:
21
+"     * Redistributions of source code must retain the above copyright notice,
22
+"       this list of conditions and the following disclaimer.
23
+"     * Redistributions in binary form must reproduce the above copyright
24
+"       notice, this list of conditions and the following disclaimer in the
25
+"       documentation and/or other materials provided with the distribution.
26
+"     * The names of the contributors may not be used to endorse or promote
27
+"       products derived from this software without specific prior written
28
+"       permission.
29
+"
30
+" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
31
+" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
+" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
33
+" NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
34
+" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35
+" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36
+" OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37
+" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38
+" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39
+" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
+
41
+" Stupid vimscript crap                                                   {{{1
42
+let s:savecpo = &cpo
43
+set cpo&vim
44
+
45
+" Private Functions                                                       {{{1
46
+
47
+" Return the number of bytes in a string after expanding tabs to spaces.  {{{2
48
+" This expansion is done based on the current value of 'tabstop'
49
+if exists('*strdisplaywidth')
50
+  " Needs vim 7.3
51
+  let s:Strlen = function("strdisplaywidth")
52
+else
53
+  function! s:Strlen(string)
54
+    " Implement the tab handling part of strdisplaywidth for vim 7.2 and
55
+    " earlier - not much that can be done about handling doublewidth
56
+    " characters.
57
+    let rv = 0
58
+    let i = 0
59
+
60
+    for char in split(a:string, '\zs')
61
+      if char == "\t"
62
+        let rv += &ts - i
63
+        let i = 0
64
+      else
65
+        let rv += 1
66
+        let i = (i + 1) % &ts
67
+      endif
68
+    endfor
69
+
70
+    return rv
71
+  endfunction
72
+endif
73
+
74
+" Align a string within a field                                           {{{2
75
+" These functions do not trim leading and trailing spaces.
76
+
77
+" Right align 'string' in a field of size 'fieldwidth'
78
+function! s:Right(string, fieldwidth)
79
+  let spaces = a:fieldwidth - s:Strlen(a:string)
80
+  return matchstr(a:string, '^\s*') . repeat(" ", spaces) . substitute(a:string, '^\s*', '', '')
81
+endfunction
82
+
83
+" Left align 'string' in a field of size 'fieldwidth'
84
+function! s:Left(string, fieldwidth)
85
+  let spaces = a:fieldwidth - s:Strlen(a:string)
86
+  return a:string . repeat(" ", spaces)
87
+endfunction
88
+
89
+" Center align 'string' in a field of size 'fieldwidth'
90
+function! s:Center(string, fieldwidth)
91
+  let spaces = a:fieldwidth - s:Strlen(a:string)
92
+  let right = spaces / 2
93
+  let left = right + (right * 2 != spaces)
94
+  return repeat(" ", left) . a:string . repeat(" ", right)
95
+endfunction
96
+
97
+" Remove spaces around a string                                           {{{2
98
+
99
+" Remove all trailing spaces from a string.
100
+function! s:StripTrailingSpaces(string)
101
+  return matchstr(a:string, '^.\{-}\ze\s*$')
102
+endfunction
103
+
104
+" Remove all leading spaces from a string.
105
+function! s:StripLeadingSpaces(string)
106
+  return matchstr(a:string, '^\s*\zs.*$')
107
+endfunction
108
+
109
+" Find the longest common indent for a list of strings                    {{{2
110
+" If a string is shorter than the others but contains no non-whitespace
111
+" characters, it does not end the match.  This provides consistency with
112
+" vim's behavior that blank lines don't have trailing spaces.
113
+function! s:LongestCommonIndent(strings)
114
+  if empty(a:strings)
115
+    return ''
116
+  endif
117
+
118
+  let n = 0
119
+  while 1
120
+    let ns = join(map(copy(a:strings), 'v:val[n]'), '')
121
+    if ns !~ '^ \+$\|^\t\+$'
122
+      break
123
+    endif
124
+    let n += 1
125
+  endwhile
126
+
127
+  return strpart(a:strings[0], 0, n)
128
+endfunction
129
+
130
+" Split a string into fields and delimiters                               {{{2
131
+" Like split(), but include the delimiters as elements
132
+" All odd numbered elements are delimiters
133
+" All even numbered elements are non-delimiters (including zero)
134
+function! s:SplitDelim(string, delim)
135
+  let rv = []
136
+  let beg = 0
137
+
138
+  let len = len(a:string)
139
+  let searchoff = 0
140
+
141
+  while 1
142
+    let mid = match(a:string, a:delim, beg + searchoff, 1)
143
+    if mid == -1 || mid == len
144
+      break
145
+    endif
146
+
147
+    let matchstr = matchstr(a:string, a:delim, beg + searchoff, 1)
148
+    let length = strlen(matchstr)
149
+
150
+    if length == 0 && beg == mid
151
+      " Zero-length match for a zero-length delimiter - advance past it
152
+      let searchoff += 1
153
+      continue
154
+    endif
155
+
156
+    if beg == mid
157
+      let rv += [ "" ]
158
+    else
159
+      let rv += [ a:string[beg : mid-1] ]
160
+    endif
161
+
162
+    let rv += [ matchstr ]
163
+
164
+    let beg = mid + length
165
+    let searchoff = 0
166
+  endwhile
167
+
168
+  let rv += [ strpart(a:string, beg) ]
169
+
170
+  return rv
171
+endfunction
172
+
173
+" Replace lines from `start' to `start + len - 1' with the given strings. {{{2
174
+" If more lines are needed to show all strings, they will be added.
175
+" If there are too few strings to fill all lines, lines will be removed.
176
+function! s:SetLines(start, len, strings)
177
+  if a:start > line('$') + 1 || a:start < 1
178
+    throw "Invalid start line!"
179
+  endif
180
+
181
+  if len(a:strings) > a:len
182
+    let fensave = &fen
183
+    let view = winsaveview()
184
+    call append(a:start + a:len - 1, repeat([''], len(a:strings) - a:len))
185
+    call winrestview(view)
186
+    let &fen = fensave
187
+  elseif len(a:strings) < a:len
188
+    let fensave = &fen
189
+    let view = winsaveview()
190
+    sil exe (a:start + len(a:strings)) . ',' .  (a:start + a:len - 1) . 'd_'
191
+    call winrestview(view)
192
+    let &fen = fensave
193
+  endif
194
+
195
+  call setline(a:start, a:strings)
196
+endfunction
197
+
198
+" Runs the given commandstring argument as an expression.                 {{{2
199
+" The commandstring expression is expected to reference the a:lines argument.
200
+" If the commandstring expression returns a list the items of that list will
201
+" replace the items in a:lines, otherwise the expression is assumed to have
202
+" modified a:lines itself.
203
+function! s:FilterString(lines, commandstring)
204
+  exe 'let rv = ' . a:commandstring
205
+
206
+  if type(rv) == type(a:lines) && rv isnot a:lines
207
+    call filter(a:lines, 0)
208
+    call extend(a:lines, rv)
209
+  endif
210
+endfunction
211
+
212
+" Public API                                                              {{{1
213
+
214
+if !exists("g:tabular_default_format")
215
+  let g:tabular_default_format = "l1"
216
+endif
217
+
218
+let s:formatelempat = '\%([lrc]\d\+\)'
219
+
220
+function! tabular#ElementFormatPattern()
221
+  return s:formatelempat
222
+endfunction
223
+
224
+" Given a list of strings and a delimiter, split each string on every
225
+" occurrence of the delimiter pattern, format each element according to either
226
+" the provided format (optional) or the default format, and join them back
227
+" together with enough space padding to guarantee that the nth delimiter of
228
+" each string is aligned.
229
+function! tabular#TabularizeStrings(strings, delim, ...)
230
+  if a:0 > 1
231
+    echoerr "TabularizeStrings accepts only 2 or 3 arguments (got ".(a:0+2).")"
232
+    return 1
233
+  endif
234
+
235
+  let formatstr = (a:0 ? a:1 : g:tabular_default_format)
236
+
237
+  if formatstr !~? s:formatelempat . '\+'
238
+    echoerr "Tabular: Invalid format \"" . formatstr . "\" specified!"
239
+    return 1
240
+  endif
241
+
242
+  let format = split(formatstr, s:formatelempat . '\zs')
243
+
244
+  let lines = a:strings
245
+
246
+  call map(lines, 's:SplitDelim(v:val, a:delim)')
247
+
248
+  let first_fields = []
249
+
250
+  " Strip spaces from non-delimiters; spaces in delimiters must have been
251
+  " matched intentionally
252
+  for line in lines
253
+    if len(line) == 1 && s:do_gtabularize
254
+      continue " Leave non-matching lines unchanged for GTabularize
255
+    endif
256
+
257
+    call add(first_fields, line[0])
258
+
259
+    if len(line) >= 1
260
+      for i in range(0, len(line)-1, 2)
261
+        let line[i] = s:StripLeadingSpaces(s:StripTrailingSpaces(line[i]))
262
+      endfor
263
+    endif
264
+  endfor
265
+
266
+  let common_indent = s:LongestCommonIndent(first_fields)
267
+
268
+  " Find the max length of each field
269
+  let maxes = []
270
+  for line in lines
271
+    if len(line) == 1 && s:do_gtabularize
272
+      continue " non-matching lines don't affect field widths for GTabularize
273
+    endif
274
+
275
+    for i in range(len(line))
276
+      if i == len(maxes)
277
+        let maxes += [ s:Strlen(line[i]) ]
278
+      else
279
+        let maxes[i] = max( [ maxes[i], s:Strlen(line[i]) ] )
280
+      endif
281
+    endfor
282
+  endfor
283
+
284
+  " Concatenate the fields, according to the format pattern.
285
+  for idx in range(len(lines))
286
+    let line = lines[idx]
287
+
288
+    if len(line) == 1 && s:do_gtabularize
289
+      let lines[idx] = line[0] " GTabularize doesn't change non-matching lines
290
+      continue
291
+    endif
292
+
293
+    for i in range(len(line))
294
+      let how = format[i % len(format)][0]
295
+      let pad = format[i % len(format)][1:-1]
296
+
297
+      if how =~? 'l'
298
+        let field = s:Left(line[i], maxes[i])
299
+      elseif how =~? 'r'
300
+        let field = s:Right(line[i], maxes[i])
301
+      elseif how =~? 'c'
302
+        let field = s:Center(line[i], maxes[i])
303
+      endif
304
+
305
+      let line[i] = field . repeat(" ", pad)
306
+    endfor
307
+
308
+    let prefix = common_indent
309
+    if len(line) == 1 && s:do_gtabularize
310
+      " We didn't strip the indent in this case; nothing to put back.
311
+      let prefix = ''
312
+    endif
313
+
314
+    let lines[idx] = s:StripTrailingSpaces(prefix . join(line, ''))
315
+  endfor
316
+endfunction
317
+
318
+" Apply 0 or more filters, in sequence, to selected text in the buffer    {{{2
319
+" The lines to be filtered are determined as follows:
320
+"   If the function is called with a range containing multiple lines, then
321
+"     those lines will be used as the range.
322
+"   If the function is called with no range or with a range of 1 line, then
323
+"     if GTabularize mode is being used,
324
+"       the range will not be adjusted
325
+"     if "includepat" is not specified,
326
+"       that 1 line will be filtered,
327
+"     if "includepat" is specified and that line does not match it,
328
+"       no lines will be filtered
329
+"     if "includepat" is specified and that line does match it,
330
+"       all contiguous lines above and below the specified line matching the
331
+"       pattern will be filtered.
332
+"
333
+" The remaining arguments must each be a filter to apply to the text.
334
+" Each filter must either be a String evaluating to a function to be called.
335
+function! tabular#PipeRange(includepat, ...) range
336
+  exe a:firstline . ',' . a:lastline
337
+      \ . 'call tabular#PipeRangeWithOptions(a:includepat, a:000, {})'
338
+endfunction
339
+
340
+" Extended version of tabular#PipeRange, which
341
+" 1) Takes the list of filters as an explicit list rather than as varargs
342
+" 2) Supports passing a dictionary of options to control the routine.
343
+"    Currently, the only supported option is 'mode', which determines whether
344
+"    to behave as :Tabularize or as :GTabularize
345
+" This allows me to add new features here without breaking API compatibility
346
+" in the future.
347
+function! tabular#PipeRangeWithOptions(includepat, filterlist, options) range
348
+  let top = a:firstline
349
+  let bot = a:lastline
350
+
351
+  let s:do_gtabularize = (get(a:options, 'mode', '') ==# 'GTabularize')
352
+
353
+  if !s:do_gtabularize
354
+    " In the default mode, apply range extension logic
355
+    if a:includepat != '' && top == bot
356
+      if top < 0 || top > line('$') || getline(top) !~ a:includepat
357
+        return
358
+      endif
359
+      while top > 1 && getline(top-1) =~ a:includepat
360
+        let top -= 1
361
+      endwhile
362
+      while bot < line('$') && getline(bot+1) =~ a:includepat
363
+        let bot += 1
364
+      endwhile
365
+    endif
366
+  endif
367
+
368
+  let lines = map(range(top, bot), 'getline(v:val)')
369
+
370
+  for filter in a:filterlist
371
+    if type(filter) != type("")
372
+      echoerr "PipeRange: Bad filter: " . string(filter)
373
+    endif
374
+
375
+    call s:FilterString(lines, filter)
376
+
377
+    unlet filter
378
+  endfor
379
+
380
+  call s:SetLines(top, bot - top + 1, lines)
381
+endfunction
382
+
383
+" Part of the public interface so interested pipelines can query this and
384
+" adjust their behavior appropriately.
385
+function! tabular#DoGTabularize()
386
+  return s:do_gtabularize
387
+endfunction
388
+
389
+function! s:SplitDelimTest(string, delim, expected)
390
+  let result = s:SplitDelim(a:string, a:delim)
391
+
392
+  if result !=# a:expected
393
+    echomsg 'Test failed!'
394
+    echomsg '  string=' . string(a:string) . '  delim=' . string(a:delim)
395
+    echomsg '  Returned=' . string(result)
396
+    echomsg '  Expected=' . string(a:expected)
397
+  endif
398
+endfunction
399
+
400
+function! tabular#SplitDelimUnitTest()
401
+  let assignment = '[|&+*/%<>=!~-]\@<!\([<>!=]=\|=\~\)\@![|&+*/%<>=!~-]*='
402
+  let two_spaces = '  '
403
+  let ternary_operator = '^.\{-}\zs?\|:'
404
+  let cpp_io = '<<\|>>'
405
+  let pascal_assign = ':='
406
+  let trailing_c_comments = '\/\*\|\*\/\|\/\/'
407
+
408
+  call s:SplitDelimTest('a+=b',    assignment, ['a', '+=', 'b'])
409
+  call s:SplitDelimTest('a-=b',    assignment, ['a', '-=', 'b'])
410
+  call s:SplitDelimTest('a!=b',    assignment, ['a!=b'])
411
+  call s:SplitDelimTest('a==b',    assignment, ['a==b'])
412
+  call s:SplitDelimTest('a&=b',    assignment, ['a', '&=', 'b'])
413
+  call s:SplitDelimTest('a|=b',    assignment, ['a', '|=', 'b'])
414
+  call s:SplitDelimTest('a=b=c',   assignment, ['a', '=', 'b', '=', 'c'])
415
+
416
+  call s:SplitDelimTest('a  b  c', two_spaces, ['a', '  ', 'b', '  ', 'c'])
417
+  call s:SplitDelimTest('a b   c', two_spaces, ['a b', '  ', ' c'])
418
+  call s:SplitDelimTest('ab    c', two_spaces, ['ab', '  ', '', '  ', 'c'])
419
+
420
+  call s:SplitDelimTest('a?b:c',   ternary_operator, ['a', '?', 'b', ':', 'c'])
421
+
422
+  call s:SplitDelimTest('a<<b<<c', cpp_io, ['a', '<<', 'b', '<<', 'c'])
423
+
424
+  call s:SplitDelimTest('a:=b=c',  pascal_assign, ['a', ':=', 'b=c'])
425
+
426
+  call s:SplitDelimTest('x//foo',  trailing_c_comments, ['x', '//', 'foo'])
427
+  call s:SplitDelimTest('x/*foo*/',trailing_c_comments, ['x', '/*', 'foo', '*/', ''])
428
+
429
+  call s:SplitDelimTest('#ab#cd#ef', '[^#]*', ['#', 'ab', '#', 'cd', '#', 'ef', ''])
430
+  call s:SplitDelimTest('#ab#cd#ef', '#\zs',  ['#', '', 'ab#', '', 'cd#', '', 'ef'])
431
+endfunction
432
+
433
+" Stupid vimscript crap, part 2                                           {{{1
434
+let &cpo = s:savecpo
435
+unlet s:savecpo
436
+
437
+" vim:set sw=2 sts=2 fdm=marker:
... ...
@@ -0,0 +1,260 @@
1
+*Tabular.txt*   Configurable, flexible, intuitive text aligning
2
+
3
+                                                       *tabular* *tabular.vim*
4
+
5
+       #|#|#|#|#|          #|                  #|                     ~
6
+           #|      #|#|#|  #|#|#|    #|    #|  #|    #|#|#|  #|  #|#| ~
7
+           #|    #|    #|  #|    #|  #|    #|  #|  #|    #|  #|#|     ~
8
+           #|    #|    #|  #|    #|  #|    #|  #|  #|    #|  #|       ~
9
+           #|      #|#|#|  #|#|#|      #|#|#|  #|    #|#|#|  #|       ~
10
+
11
+                                                  For Vim version 7.0 or newer
12
+
13
+                               By Matt Wozniski
14
+                                mjw@drexel.edu
15
+
16
+                               Reference Manual ~
17
+
18
+                                                                 *tabular-toc*
19
+
20
+1. Description                                           |tabular-intro|
21
+2. Walkthrough                                           |tabular-walkthrough|
22
+3. Scripting                                             |tabular-scripting|
23
+
24
+The functionality mentioned here is a plugin, see |add-plugin|.
25
+You can avoid loading this plugin by setting the "Tabular_loaded" global
26
+variable in your |vimrc| file: >
27
+    :let g:tabular_loaded = 1
28
+
29
+==============================================================================
30
+1. Description                                                 *tabular-intro*
31
+
32
+Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
33
+computer do this for you, since aligning things by hand quickly becomes
34
+unpleasant.  While there are other plugins for aligning text, the ones I've
35
+tried are either impossibly difficult to understand and use, or too simplistic
36
+to handle complicated tasks.  This plugin aims to make the easy things easy
37
+and the hard things possible, without providing an unnecessarily obtuse
38
+interface.  It's still a work in progress, and criticisms are welcome.
39
+
40
+==============================================================================
41
+2. Walkthrough                             *tabular-walkthrough* *:Tabularize*
42
+
43
+Tabular's commands are based largely on regular expressions.  The basic
44
+technique used by Tabular is taking some regex to match field delimiters,
45
+splitting the input lines at those delimiters, trimming unnecessary spaces
46
+from the non-delimiter parts, padding the non-delimiter parts of the lines
47
+with spaces to make them the same length, and joining things back together
48
+again.
49
+
50
+For instance, consider starting with the following lines:
51
+>
52
+    Some short phrase,some other phrase
53
+    A much longer phrase here,and another long phrase
54
+<
55
+Let's say we want to line these lines up at the commas.  We can tell
56
+Tabularize to do this by passing a pattern matching , to the Tabularize
57
+command:
58
+>
59
+  :Tabularize /,
60
+
61
+    Some short phrase         , some other phrase
62
+    A much longer phrase here , and another long phrase
63
+<
64
+I encourage you to try copying those lines to another buffer and trying to
65
+call :Tabularize.  You'll want to take notice of two things quickly: First,
66
+instead of requiring a range, Tabularize tries to figure out what you want to
67
+happen.  Since it knows that you want to act on lines matching a comma, it
68
+will look upwards and downwards for lines around the current line that match a
69
+comma, and consider all contiguous lines matching the pattern to be the range
70
+to be acted upon.  You can always override this by specifying a range, though.
71
+
72
+The second thing you should notice is that you'll almost certainly be able to
73
+abbreviate :Tabularize to :Tab - using this form in mappings and scripts is
74
+discouraged as it will make conflicts with other scripts more likely, but for
75
+interactive use it's a nice timesaver.  Another convenience feature is that
76
+running :Tabularize without providing a new pattern will cause it to reuse the
77
+last pattern it was called with.
78
+
79
+So, anyway, now the commas line up.  Splitting the lines on commas, Tabular
80
+realized that 'Some short phrase' would need to be padded with spaces to match
81
+the length of 'A much longer phrase here', and it did that before joining the
82
+lines back together.  You'll also notice that, in addition to the spaces
83
+inserting for padding, extra spaces were inserted between fields.  That's
84
+because by default, Tabular prints things left-aligned with one space between
85
+fields.  If you wanted to print things right-aligned with no spaces between
86
+fields, you would provide a different format to the Tabularize command:
87
+>
88
+  :Tabularize /,/r0
89
+
90
+            Some short phrase,      some other phrase
91
+    A much longer phrase here,and another long phrase
92
+<
93
+A format specifier is either l, r, or c, followed by one or more digits.  If
94
+the letter is l, the field will be left aligned, similarly for r and right
95
+aligning and c and center aligning.  The number following the letter is the
96
+number of spaces padding to insert before the start of the next field.
97
+Multiple format specifiers can be added to the same command - each field will
98
+be printed with the next format specifier in the list; when they all have been
99
+used the first will be used again, and so on.  So, the last command right
100
+aligned every field, then inserted 0 spaces of padding before the next field.
101
+What if we wanted to right align the text before the comma, and left align the
102
+text after the comma?  The command would look like this:
103
+>
104
+  :Tabularize /,/r1c1l0
105
+
106
+            Some short phrase , some other phrase
107
+    A much longer phrase here , and another long phrase
108
+<
109
+That command would be read as "Align the matching text, splitting fields on
110
+commas.  Print everything before the first comma right aligned, then 1 space,
111
+then the comma center aligned, then 1 space, then everything after the comma
112
+left aligned."  Notice that the alignment of the field the comma is in is
113
+irrelevant - since it's only 1 cell wide, it looks the same whether it's right,
114
+left, or center aligned.  Also notice that the 0 padding spaces specified for
115
+the 3rd field are unused - but they would be used if there were enough fields
116
+to require looping through the fields again.  For instance:
117
+>
118
+    abc,def,ghi
119
+    a,b
120
+    a,b,c
121
+
122
+  :Tabularize /,/r1c1l0
123
+
124
+    abc , def, ghi
125
+      a , b
126
+      a , b  ,  c
127
+<
128
+Notice that now, the format pattern has been reused; field 4 (the second comma)
129
+is right aligned, field 5 is center aligned.  No spaces were inserted between
130
+the 3rd field (containing "def") and the 4th field (the second comma) because
131
+the format specified 'l0'.
132
+
133
+But, what if you only wanted to act on the first comma on the line, rather than
134
+all of the commas on the line?  Let's say we want everything before the first
135
+comma right aligned, then the comma, then everything after the comma left
136
+aligned:
137
+>
138
+    abc,def,ghi
139
+    a,b
140
+    a,b,c
141
+
142
+  :Tabularize /^[^,]*\zs,/r0c0l0
143
+
144
+    abc,def,ghi
145
+      a,b
146
+      a,b,c
147
+<
148
+Here, we used a Vim regex that would only match the first comma on the line.
149
+It matches the beginning of the line, followed by all the non-comma characters
150
+up to the first comma, and then forgets about what it matched so far and
151
+pretends that the match starts exactly at the comma.
152
+
153
+But, now that this command does exactly what we want it to, it's become pretty
154
+unwieldy.  It would be unpleasant to need to type that more than once or
155
+twice.  The solution is to assign a name to it.
156
+>
157
+  :AddTabularPattern first_comma /^[^,]*\zs,/r0c0l0
158
+<
159
+Now, typing ":Tabularize first_comma" will do the same thing as typing the
160
+whole pattern out each time.  Of course this is more useful if you store the
161
+name in a file to be used later.
162
+
163
+NOTE: In order to make these new commands available every time vim starts,
164
+you'll need to put those new commands into a .vim file in a plugin directory
165
+somewhere in your 'runtimepath'.  In order to make sure that Tabular.vim has
166
+already been loaded before your file tries to use :AddTabularPattern or
167
+:AddTabularPipeline, the new file should be installed in an after/plugin
168
+directory in 'runtimepath'.  In general, it will be safe to find out where the
169
+TabularMaps.vim plugin was installed, and place other files extending
170
+Tabular.vim in the same directory as TabularMaps.vim.  For more information,
171
+and some suggested best practices, check out the |tabular-scripting| section.
172
+
173
+Lastly, we'll approach the case where tabular cannot achieve your desired goal
174
+just by splitting lines apart, trimming whitespace, padding with whitespace,
175
+and rejoining the lines.  As an example, consider the multiple_spaces command
176
+from TabularMaps.vim.  The goal is to split using two or more spaces as a
177
+field delimiter, and join fields back together, properly lined up, with only
178
+two spaces between the end of each field and the beginning of the next.
179
+Unfortunately, Tabular can't do this with only the commands we know so far:
180
+>
181
+  :Tabularize /  /
182
+<
183
+The above function won't work, because it will consider "a    b" as 5 fields
184
+delimited by two pairs of 2 spaces ( 'a', '  ', '', '  ', 'b' ) instead of as
185
+3 fields delimited by one set of 2 or more spaces ( 'a', '    ', 'b' ).
186
+>
187
+  :Tabularize /  \+/
188
+<
189
+The above function won't work either, because it will leave the delimiter as 4
190
+spaces when used against "a    b", meaning that we would fail at our goal of
191
+collapsing everything down to two spaces between fields.  So, we need a new
192
+command to get around this:
193
+>
194
+  :AddTabularPipeline multiple_spaces / \{2,}/
195
+    \ map(a:lines, "substitute(v:val, ' \{2,}', '  ', 'g')")
196
+    \   | tabular#TabularizeStrings(a:lines, '  ', 'l0')
197
+<
198
+Yeah.  I know it looks complicated.  Bear with me.  I probably will try to add
199
+in some shortcuts for this syntax, but this verbose will be guaranteed to
200
+always work.
201
+
202
+You should already recognize the name being assigned.  The next thing to
203
+happen is / \{2,}/ which is a pattern specifying which lines should
204
+automatically be included in the range when no range is given.  Without this,
205
+there would be no pattern to use for extending the range.  Everything after
206
+that is a | separated list of expressions to be evaluated.  In the context in
207
+which they will be evaluated, a:lines will be set to a List of Strings
208
+containing the text of the lines being filtered as they procede through the
209
+pipeline you've set up.  The \ at the start of the lines are just vim's line
210
+continuation marker; you needn't worry much about them.  So, the first
211
+expression in the pipeline transforms each line by replacing every instance of
212
+2 or more spaces with exactly two spaces.  The second command in the pipeline
213
+performs the equivalent of ":Tabularize /  /l0"; the only difference is that
214
+it is operating on a List of Strings rather than text in the buffer.  At the
215
+end of the pipeline, the Strings in the modified a:lines (or the return value
216
+of the last expression in the pipeline, if it returns a List) will replace the
217
+chosen range.
218
+
219
+==============================================================================
220
+3. Extending                                               *tabular-scripting*
221
+
222
+As mentioned above, the most important consideration when extending Tabular
223
+with new maps or commands is that your plugin must be loaded after Tabular.vim
224
+has finished loading, and only if Tabular.vim has loaded successfully.  The
225
+easiest approach to making sure it loads after Tabular.vim is simply putting
226
+the new file (we'll call it "tabular_extra.vim" as an example) into an
227
+"after/plugin/" directory in 'runtimepath', for instance:
228
+>
229
+  ~/.vim/after/plugin/tabular_extra.vim
230
+<
231
+The default set of mappings, found in "TabularMaps.vim", is installed in
232
+the after/plugin/ subdirectory of whatever directory Tabular was installed to.
233
+
234
+The other important consideration is making sure that your commands are only
235
+called if Tabular.vim was actually loaded.  The easiest way to do this is by
236
+checking for the existence of the :Tabularize command at the start of your
237
+plugin.  A short example plugin would look like this:
238
+>
239
+  " after/plugin/my_tabular_commands.vim
240
+  " Provides extra :Tabularize commands
241
+
242
+  if !exists(':Tabularize')
243
+    finish " Give up here; the Tabular plugin musn't have been loaded
244
+  endif
245
+
246
+  " Make line wrapping possible by resetting the 'cpo' option, first saving it
247
+  let s:save_cpo = &cpo
248
+  set cpo&vim
249
+
250
+  AddTabularPattern! asterisk /*/l1
251
+
252
+  AddTabularPipeline! remove_leading_spaces /^ /
253
+                  \ map(a:lines, "substitute(v:val, '^ *', '', '')")
254
+
255
+  " Restore the saved value of 'cpo'
256
+  let &cpo = s:save_cpo
257
+  unlet s:save_cpo
258
+<
259
+==============================================================================
260
+vim:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\":ts=8:ft=help:norl:
... ...
@@ -0,0 +1,350 @@
1
+" Tabular:     Align columnar data using regex-designated column boundaries
2
+" Maintainer:  Matthew Wozniski (godlygeek@gmail.com)
3
+" Date:        Thu, 03 May 2012 20:49:32 -0400
4
+" Version:     1.0
5
+"
6
+" Long Description:
7
+" Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
8
+" computer do this for you, since aligning things by hand quickly becomes
9
+" unpleasant.  While there are other plugins for aligning text, the ones I've
10
+" tried are either impossibly difficult to understand and use, or too simplistic
11
+" to handle complicated tasks.  This plugin aims to make the easy things easy
12
+" and the hard things possible, without providing an unnecessarily obtuse
13
+" interface.  It's still a work in progress, and criticisms are welcome.
14
+"
15
+" License:
16
+" Copyright (c) 2012, Matthew J. Wozniski
17
+" All rights reserved.
18
+"
19
+" Redistribution and use in source and binary forms, with or without
20
+" modification, are permitted provided that the following conditions are met:
21
+"     * Redistributions of source code must retain the above copyright notice,
22
+"       this list of conditions and the following disclaimer.
23
+"     * Redistributions in binary form must reproduce the above copyright
24
+"       notice, this list of conditions and the following disclaimer in the
25
+"       documentation and/or other materials provided with the distribution.
26
+"     * The names of the contributors may not be used to endorse or promote
27
+"       products derived from this software without specific prior written
28
+"       permission.
29
+"
30
+" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
31
+" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
+" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
33
+" NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
34
+" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35
+" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36
+" OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37
+" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38
+" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39
+" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
+
41
+" Abort if running in vi-compatible mode or the user doesn't want us.
42
+if &cp || exists('g:tabular_loaded')
43
+  if &cp && &verbose
44
+    echo "Not loading Tabular in compatible mode."
45
+  endif
46
+  finish
47
+endif
48
+
49
+let g:tabular_loaded = 1
50
+
51
+" Stupid vimscript crap                                                   {{{1
52
+let s:savecpo = &cpo
53
+set cpo&vim
54
+
55
+" Private Things                                                          {{{1
56
+
57
+" Dictionary of command name to command
58
+let s:TabularCommands = {}
59
+
60
+" Generate tab completion list for :Tabularize                            {{{2
61
+" Return a list of commands that match the command line typed so far.
62
+" NOTE: Tries to handle commands with spaces in the name, but Vim doesn't seem
63
+"       to handle that terribly well... maybe I should give up on that.
64
+function! s:CompleteTabularizeCommand(argstart, cmdline, cursorpos)
65
+  let names = keys(s:TabularCommands)
66
+  if exists("b:TabularCommands")
67
+    let names += keys(b:TabularCommands)
68
+  endif
69
+
70
+  let cmdstart = substitute(a:cmdline, '^\s*\S\+\s*', '', '')
71
+
72
+  return filter(names, 'v:val =~# ''^\V'' . escape(cmdstart, ''\'')')
73
+endfunction
74
+
75
+" Choose the proper command map from the given command line               {{{2
76
+" Returns [ command map, command line with leading <buffer> removed ]
77
+function! s:ChooseCommandMap(commandline)
78
+  let map = s:TabularCommands
79
+  let cmd = a:commandline
80
+
81
+  if cmd =~# '^<buffer>\s\+'
82
+    if !exists('b:TabularCommands')
83
+      let b:TabularCommands = {}
84
+    endif
85
+    let map = b:TabularCommands
86
+    let cmd = substitute(cmd, '^<buffer>\s\+', '', '')
87
+  endif
88
+
89
+  return [ map, cmd ]
90
+endfunction
91
+
92
+" Parse '/pattern/format' into separate pattern and format parts.         {{{2
93
+" If parsing fails, return [ '', '' ]
94
+function! s:ParsePattern(string)
95
+  if a:string[0] != '/'
96
+    return ['','']
97
+  endif
98
+
99
+  let pat = '\\\@<!\%(\\\\\)\{-}\zs/' . tabular#ElementFormatPattern() . '*$'
100
+  let format = matchstr(a:string[1:-1], pat)
101
+  if !empty(format)
102
+    let format = format[1 : -1]
103
+    let pattern = a:string[1 : -len(format) - 2]
104
+  else
105
+    let pattern = a:string[1 : -1]
106
+  endif
107
+
108
+  return [pattern, format]
109
+endfunction
110
+
111
+" Split apart a list of | separated expressions.                          {{{2
112
+function! s:SplitCommands(string)
113
+  if a:string =~ '^\s*$'
114
+    return []
115
+  endif
116
+
117
+  let end = match(a:string, "[\"'|]")
118
+
119
+  " Loop until we find a delimiting | or end-of-string
120
+  while end != -1 && (a:string[end] != '|' || a:string[end+1] == '|')
121
+    if a:string[end] == "'"
122
+      let end = match(a:string, "'", end+1) + 1
123
+      if end == 0
124
+        throw "No matching end single quote"
125
+      endif
126
+    elseif a:string[end] == '"'
127
+      " Find a " preceded by an even number of \ (or 0)
128
+      let pattern = '\%(\\\@<!\%(\\\\\)*\)\@<="'
129
+      let end = matchend(a:string, pattern, end+1) + 1
130
+      if end == 0
131
+        throw "No matching end double quote"
132
+      endif
133
+    else " Found ||
134
+      let end += 2
135
+    endif
136
+
137
+    let end = match(a:string, "[\"'|]", end)
138
+  endwhile
139
+
140
+  if end == 0 || a:string[0 : end - (end > 0)] =~ '^\s*$'
141
+    throw "Empty element"
142
+  endif
143
+
144
+  if end == -1
145
+    let rv = [ a:string ]
146
+  else
147
+    let rv = [ a:string[0 : end-1] ] + s:SplitCommands(a:string[end+1 : -1])
148
+  endif
149
+
150
+  return rv
151
+endfunction
152
+
153
+" Public Things                                                           {{{1
154
+
155
+" Command associating a command name with a simple pattern command        {{{2
156
+" AddTabularPattern[!] [<buffer>] name /pattern[/format]
157
+"
158
+" If <buffer> is provided, the command will only be available in the current
159
+" buffer, and will be used instead of any global command with the same name.
160
+"
161
+" If a command with the same name and scope already exists, it is an error,
162
+" unless the ! is provided, in which case the existing command will be
163
+" replaced.
164
+"
165
+" pattern is a regex describing the delimiter to be used.
166
+"
167
+" format describes the format pattern to be used.  The default will be used if
168
+" none is provided.
169
+com! -nargs=+ -bang AddTabularPattern
170
+   \ call AddTabularPattern(<q-args>, <bang>0)
171
+
172
+function! AddTabularPattern(command, force)
173
+  try
174
+    let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
175
+
176
+    let name = matchstr(rest, '.\{-}\ze\s*/')
177
+    let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
178
+
179
+    let [ pattern, format ] = s:ParsePattern(pattern)
180
+
181
+    if empty(name) || empty(pattern)
182
+      throw "Invalid arguments!"
183
+    endif
184
+
185
+    if !a:force && has_key(commandmap, name)
186
+      throw string(name) . " is already defined, use ! to overwrite."
187
+    endif
188
+
189
+    let command = "tabular#TabularizeStrings(a:lines, " . string(pattern)
190
+
191
+    if !empty(format)
192
+      let command .=  ", " . string(format)
193
+    endif
194
+
195
+    let command .= ")"
196
+
197
+    let commandmap[name] = { 'pattern' : pattern, 'commands' : [ command ] }
198
+  catch
199
+    echohl ErrorMsg
200
+    echomsg "AddTabularPattern: " . v:exception
201
+    echohl None
202
+  endtry
203
+endfunction
204
+
205
+" Command associating a command name with a pipeline of functions         {{{2
206
+" AddTabularPipeline[!] [<buffer>] name /pattern/ func [ | func2 [ | func3 ] ]
207
+"
208
+" If <buffer> is provided, the command will only be available in the current
209
+" buffer, and will be used instead of any global command with the same name.
210
+"
211
+" If a command with the same name and scope already exists, it is an error,
212
+" unless the ! is provided, in which case the existing command will be
213
+" replaced.
214
+"
215
+" pattern is a regex that will be used to determine which lines will be
216
+" filtered.  If the cursor line doesn't match the pattern, using the command
217
+" will be a no-op, otherwise the cursor and all contiguous lines matching the
218
+" pattern will be filtered.
219
+"
220
+" Each 'func' argument represents a function to be called.  This function
221
+" will have access to a:lines, a List containing one String per line being
222
+" filtered.
223
+com! -nargs=+ -bang AddTabularPipeline
224
+   \ call AddTabularPipeline(<q-args>, <bang>0)
225
+
226
+function! AddTabularPipeline(command, force)
227
+  try
228
+    let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
229
+
230
+    let name = matchstr(rest, '.\{-}\ze\s*/')
231
+    let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
232
+
233
+    let commands = matchstr(pattern, '^/.\{-}\\\@<!\%(\\\\\)\{-}/\zs.*')
234
+    let pattern = matchstr(pattern, '/\zs.\{-}\\\@<!\%(\\\\\)\{-}\ze/')
235
+
236
+    if empty(name) || empty(pattern)
237
+      throw "Invalid arguments!"
238
+    endif
239
+
240
+    if !a:force && has_key(commandmap, name)
241
+      throw string(name) . " is already defined, use ! to overwrite."
242
+    endif
243
+
244
+    let commandlist = s:SplitCommands(commands)
245
+
246
+    if empty(commandlist)
247
+      throw "Must provide a list of functions!"
248
+    endif
249
+
250
+    let commandmap[name] = { 'pattern' : pattern, 'commands' : commandlist }
251
+  catch
252
+    echohl ErrorMsg
253
+    echomsg "AddTabularPipeline: " . v:exception
254
+    echohl None
255
+  endtry
256
+endfunction
257
+
258
+" Tabularize /pattern[/format]                                            {{{2
259
+" Tabularize name
260
+"
261
+" Align text, either using the given pattern, or the command associated with
262
+" the given name.
263
+com! -nargs=* -range -complete=customlist,<SID>CompleteTabularizeCommand
264
+   \ Tabularize <line1>,<line2>call Tabularize(<q-args>)
265
+
266
+function! Tabularize(command, ...) range
267
+  let piperange_opt = {}
268
+  if a:0
269
+    let piperange_opt = a:1
270
+  endif
271
+
272
+  if empty(a:command)
273
+    if !exists("s:last_tabularize_command")
274
+      echohl ErrorMsg
275
+      echomsg "Tabularize hasn't been called yet; no pattern/command to reuse!"
276
+      echohl None
277
+      return
278
+    endif
279
+  else
280
+    let s:last_tabularize_command = a:command
281
+  endif
282
+
283
+  let command = s:last_tabularize_command
284
+
285
+  let range = a:firstline . ',' . a:lastline
286
+
287
+  try
288
+    let [ pattern, format ] = s:ParsePattern(command)
289
+
290
+    if !empty(pattern)
291
+      let cmd  = "tabular#TabularizeStrings(a:lines, " . string(pattern)
292
+
293
+      if !empty(format)
294
+        let cmd .= "," . string(format)
295
+      endif
296
+
297
+      let cmd .= ")"
298
+
299
+      exe range . 'call tabular#PipeRangeWithOptions(pattern, [ cmd ], '
300
+                      \ . 'piperange_opt)'
301
+    else
302
+      if exists('b:TabularCommands') && has_key(b:TabularCommands, command)
303
+        let usercmd = b:TabularCommands[command]
304
+      elseif has_key(s:TabularCommands, command)
305
+        let usercmd = s:TabularCommands[command]
306
+      else
307
+        throw "Unrecognized command " . string(command)
308
+      endif
309
+
310
+      exe range . 'call tabular#PipeRangeWithOptions(usercmd["pattern"], '
311
+                      \ . 'usercmd["commands"], piperange_opt)'
312
+    endif
313
+  catch
314
+    echohl ErrorMsg
315
+    echomsg "Tabularize: " . v:exception
316
+    echohl None
317
+    return
318
+  endtry
319
+endfunction
320
+
321
+function! TabularizeHasPattern()
322
+  return exists("s:last_tabularize_command")
323
+endfunction
324
+
325
+" GTabularize /pattern[/format]                                           {{{2
326
+" GTabularize name
327
+"
328
+" Align text on only matching lines, either using the given pattern, or the
329
+" command associated with the given name.  Mnemonically, this is similar to
330
+" the :global command, which takes some action on all rows matching a pattern
331
+" in a range.  This command is different from normal :Tabularize in 3 ways:
332
+"   1) If a line in the range does not match the pattern, it will be left
333
+"      unchanged, and not in any way affect the outcome of other lines in the
334
+"      range (at least, normally - but Pipelines can and will still look at
335
+"      non-matching rows unless they are specifically written to be aware of
336
+"      tabular#DoGTabularize() and handle it appropriately).
337
+"   2) No automatic range determination - :Tabularize automatically expands
338
+"      a single-line range (or a call with no range) to include all adjacent
339
+"      matching lines.  That behavior does not make sense for this command.
340
+"   3) If called without a range, it will act on all lines in the buffer (like
341
+"      :global) rather than only a single line
342
+com! -nargs=* -range=% -complete=customlist,<SID>CompleteTabularizeCommand
343
+   \ GTabularize <line1>,<line2>
344
+   \ call Tabularize(<q-args>, { 'mode': 'GTabularize' } )
345
+
346
+" Stupid vimscript crap, part 2                                           {{{1
347
+let &cpo = s:savecpo
348
+unlet s:savecpo
349
+
350
+" vim:set sw=2 sts=2 fdm=marker:
... ...
@@ -0,0 +1,24 @@
1
+Copyright (c) 2016, Matthew J. Wozniski
2
+All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without
5
+modification, are permitted provided that the following conditions are met:
6
+    * Redistributions of source code must retain the above copyright notice,
7
+      this list of conditions and the following disclaimer.
8
+    * Redistributions in binary form must reproduce the above copyright
9
+      notice, this list of conditions and the following disclaimer in the
10
+      documentation and/or other materials provided with the distribution.
11
+    * The names of the contributors may not be used to endorse or promote
12
+      products derived from this software without specific prior written
13
+      permission.
14
+
15
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
16
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
18
+NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
19
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
21
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
24
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
... ...
@@ -0,0 +1,38 @@
1
+Tabular
2
+==============
3
+Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
4
+computer do this for you, since aligning things by hand quickly becomes
5
+unpleasant.  While there are other plugins for aligning text, the ones I've
6
+tried are either impossibly difficult to understand and use, or too simplistic
7
+to handle complicated tasks.  This plugin aims to make the easy things easy
8
+and the hard things possible, without providing an unnecessarily obtuse
9
+interface.  It's still a work in progress, and criticisms are welcome.
10
+
11
+See [Aligning Text with Tabular.vim](http://vimcasts.org/episodes/aligning-text-with-tabular-vim/)
12
+for a screencast that shows how Tabular.vim works.
13
+
14
+See [doc/Tabular.txt](http://raw.github.com/godlygeek/tabular/master/doc/Tabular.txt)
15
+for detailed documentation.
16
+
17
+Installation
18
+==============
19
+
20
+## Vim 8.1+
21
+
22
+No third-party package manager is required! Clone into:
23
+
24
+`.vim/pack/plugins/start`
25
+
26
+Make sure you include `packloadall` in your `.vimrc`.
27
+
28
+## Pathogen
29
+
30
+    mkdir -p ~/.vim/bundle
31
+    cd ~/.vim/bundle
32
+    git clone https://github.com/godlygeek/tabular.git
33
+
34
+Once help tags have been generated (either using Pathogen's `:Helptags`
35
+command, or by pointing vim's `:helptags` command at the directory where you
36
+installed Tabular), you can view the manual with `:help tabular`.
37
+
38
+See [pathogen.vim](https://github.com/tpope/vim-pathogen) for help or for package manager installation.
... ...
@@ -0,0 +1,74 @@
1
+" Copyright (c) 2016, Matthew J. Wozniski
2
+" All rights reserved.
3
+"
4
+" Redistribution and use in source and binary forms, with or without
5
+" modification, are permitted provided that the following conditions are met:
6
+"     * Redistributions of source code must retain the above copyright notice,
7
+"       this list of conditions and the following disclaimer.
8
+"     * Redistributions in binary form must reproduce the above copyright
9
+"       notice, this list of conditions and the following disclaimer in the
10
+"       documentation and/or other materials provided with the distribution.
11
+"     * The names of the contributors may not be used to endorse or promote
12
+"       products derived from this software without specific prior written
13
+"       permission.
14
+"
15
+" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
16
+" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
+" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
18
+" NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
19
+" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
+" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
21
+" OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22
+" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23
+" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
24
+" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
26
+if !exists(':Tabularize') || get(g:, 'no_default_tabular_maps', 0)
27
+  finish " Tabular.vim wasn't loaded or the default maps are unwanted
28
+endif
29
+
30
+let s:save_cpo = &cpo
31
+set cpo&vim
32
+
33
+AddTabularPattern!  assignment      /[|&+*/%<>=!~-]\@<!\([<>!=]=\|=\~\)\@![|&+*/%<>=!~-]*=/l1r1
34
+AddTabularPattern!  two_spaces      /  /l0
35
+
36
+AddTabularPipeline! multiple_spaces /  / map(a:lines, "substitute(v:val, '   *', '  ', 'g')") | tabular#TabularizeStrings(a:lines, '  ', 'l0')
37
+AddTabularPipeline! spaces          / /  map(a:lines, "substitute(v:val, '  *' , ' ' , 'g')") | tabular#TabularizeStrings(a:lines, ' ' , 'l0')
38
+
39
+AddTabularPipeline! argument_list   /(.*)/ map(a:lines, 'substitute(v:val, ''\s*\([(,)]\)\s*'', ''\1'', ''g'')')
40
+                                       \ | tabular#TabularizeStrings(a:lines, '[(,)]', 'l0')
41
+                                       \ | map(a:lines, 'substitute(v:val, ''\(\s*\),'', '',\1 '', "g")')
42
+                                       \ | map(a:lines, 'substitute(v:val, ''\s*)'', ")", "g")')
43
+
44
+function! SplitCDeclarations(lines)
45
+  let rv = []
46
+  for line in a:lines
47
+    " split the line into declaractions
48
+    let split = split(line, '\s*[,;]\s*')
49
+    " separate the type from the first declaration
50
+    let type = substitute(split[0], '\%(\%([&*]\s*\)*\)\=\k\+$', '', '')
51
+    " add the ; back on every declaration
52
+    call map(split, 'v:val . ";"')
53
+    " add the first element to the return as-is, and remove it from the list
54
+    let rv += [ remove(split, 0) ]
55
+    " transform the other elements by adding the type on at the beginning
56
+    call map(split, 'type . v:val')
57
+    " and add them all to the return
58
+    let rv += split
59
+  endfor
60
+  return rv
61
+endfunction
62
+
63
+AddTabularPipeline! split_declarations /,.*;/ SplitCDeclarations(a:lines)
64
+
65
+AddTabularPattern! ternary_operator /^.\{-}\zs?\|:/l1
66
+
67
+AddTabularPattern! cpp_io /<<\|>>/l1
68
+
69
+AddTabularPattern! pascal_assign /:=/l1
70
+
71
+AddTabularPattern! trailing_c_comments /\/\*\|\*\/\|\/\//l1
72
+
73
+let &cpo = s:save_cpo
74
+unlet s:save_cpo
... ...
@@ -0,0 +1,437 @@
1
+" Tabular:     Align columnar data using regex-designated column boundaries
2
+" Maintainer:  Matthew Wozniski (godlygeek@gmail.com)
3
+" Date:        Thu, 03 May 2012 20:49:32 -0400
4
+" Version:     1.0
5
+"
6
+" Long Description:
7
+" Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
8
+" computer do this for you, since aligning things by hand quickly becomes
9
+" unpleasant.  While there are other plugins for aligning text, the ones I've
10
+" tried are either impossibly difficult to understand and use, or too simplistic
11
+" to handle complicated tasks.  This plugin aims to make the easy things easy
12
+" and the hard things possible, without providing an unnecessarily obtuse
13
+" interface.  It's still a work in progress, and criticisms are welcome.
14
+"
15
+" License:
16
+" Copyright (c) 2012, Matthew J. Wozniski
17
+" All rights reserved.
18
+"
19
+" Redistribution and use in source and binary forms, with or without
20
+" modification, are permitted provided that the following conditions are met:
21
+"     * Redistributions of source code must retain the above copyright notice,
22
+"       this list of conditions and the following disclaimer.
23
+"     * Redistributions in binary form must reproduce the above copyright
24
+"       notice, this list of conditions and the following disclaimer in the
25
+"       documentation and/or other materials provided with the distribution.
26
+"     * The names of the contributors may not be used to endorse or promote
27
+"       products derived from this software without specific prior written
28
+"       permission.
29
+"
30
+" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
31
+" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
+" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
33
+" NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
34
+" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35
+" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36
+" OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37
+" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38
+" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39
+" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
+
41
+" Stupid vimscript crap                                                   {{{1
42
+let s:savecpo = &cpo
43
+set cpo&vim
44
+
45
+" Private Functions                                                       {{{1
46
+
47
+" Return the number of bytes in a string after expanding tabs to spaces.  {{{2
48
+" This expansion is done based on the current value of 'tabstop'
49
+if exists('*strdisplaywidth')
50
+  " Needs vim 7.3
51
+  let s:Strlen = function("strdisplaywidth")
52
+else
53
+  function! s:Strlen(string)
54
+    " Implement the tab handling part of strdisplaywidth for vim 7.2 and
55
+    " earlier - not much that can be done about handling doublewidth
56
+    " characters.
57
+    let rv = 0
58
+    let i = 0
59
+
60
+    for char in split(a:string, '\zs')
61
+      if char == "\t"
62
+        let rv += &ts - i
63
+        let i = 0
64
+      else
65
+        let rv += 1
66
+        let i = (i + 1) % &ts
67
+      endif
68
+    endfor
69
+
70
+    return rv
71
+  endfunction
72
+endif
73
+
74
+" Align a string within a field                                           {{{2
75
+" These functions do not trim leading and trailing spaces.
76
+
77
+" Right align 'string' in a field of size 'fieldwidth'
78
+function! s:Right(string, fieldwidth)
79
+  let spaces = a:fieldwidth - s:Strlen(a:string)
80
+  return matchstr(a:string, '^\s*') . repeat(" ", spaces) . substitute(a:string, '^\s*', '', '')
81
+endfunction
82
+
83
+" Left align 'string' in a field of size 'fieldwidth'
84
+function! s:Left(string, fieldwidth)
85
+  let spaces = a:fieldwidth - s:Strlen(a:string)
86
+  return a:string . repeat(" ", spaces)
87
+endfunction
88
+
89
+" Center align 'string' in a field of size 'fieldwidth'
90
+function! s:Center(string, fieldwidth)
91
+  let spaces = a:fieldwidth - s:Strlen(a:string)
92
+  let right = spaces / 2
93
+  let left = right + (right * 2 != spaces)
94
+  return repeat(" ", left) . a:string . repeat(" ", right)
95
+endfunction
96
+
97
+" Remove spaces around a string                                           {{{2
98
+
99
+" Remove all trailing spaces from a string.
100
+function! s:StripTrailingSpaces(string)
101
+  return matchstr(a:string, '^.\{-}\ze\s*$')
102
+endfunction
103
+
104
+" Remove all leading spaces from a string.
105
+function! s:StripLeadingSpaces(string)
106
+  return matchstr(a:string, '^\s*\zs.*$')
107
+endfunction
108
+
109
+" Find the longest common indent for a list of strings                    {{{2
110
+" If a string is shorter than the others but contains no non-whitespace
111
+" characters, it does not end the match.  This provides consistency with
112
+" vim's behavior that blank lines don't have trailing spaces.
113
+function! s:LongestCommonIndent(strings)
114
+  if empty(a:strings)
115
+    return ''
116
+  endif
117
+
118
+  let n = 0
119
+  while 1
120
+    let ns = join(map(copy(a:strings), 'v:val[n]'), '')
121
+    if ns !~ '^ \+$\|^\t\+$'
122
+      break
123
+    endif
124
+    let n += 1
125
+  endwhile
126
+
127
+  return strpart(a:strings[0], 0, n)
128
+endfunction
129
+
130
+" Split a string into fields and delimiters                               {{{2
131
+" Like split(), but include the delimiters as elements
132
+" All odd numbered elements are delimiters
133
+" All even numbered elements are non-delimiters (including zero)
134
+function! s:SplitDelim(string, delim)
135
+  let rv = []
136
+  let beg = 0
137
+
138
+  let len = len(a:string)
139
+  let searchoff = 0
140
+
141
+  while 1
142
+    let mid = match(a:string, a:delim, beg + searchoff, 1)
143
+    if mid == -1 || mid == len
144
+      break
145
+    endif
146
+
147
+    let matchstr = matchstr(a:string, a:delim, beg + searchoff, 1)
148
+    let length = strlen(matchstr)
149
+
150
+    if length == 0 && beg == mid
151
+      " Zero-length match for a zero-length delimiter - advance past it
152
+      let searchoff += 1
153
+      continue
154
+    endif
155
+
156
+    if beg == mid
157
+      let rv += [ "" ]
158
+    else
159
+      let rv += [ a:string[beg : mid-1] ]
160
+    endif
161
+
162
+    let rv += [ matchstr ]
163
+
164
+    let beg = mid + length
165
+    let searchoff = 0
166
+  endwhile
167
+
168
+  let rv += [ strpart(a:string, beg) ]
169
+
170
+  return rv
171
+endfunction
172
+
173
+" Replace lines from `start' to `start + len - 1' with the given strings. {{{2
174
+" If more lines are needed to show all strings, they will be added.
175
+" If there are too few strings to fill all lines, lines will be removed.
176
+function! s:SetLines(start, len, strings)
177
+  if a:start > line('$') + 1 || a:start < 1
178
+    throw "Invalid start line!"
179
+  endif
180
+
181
+  if len(a:strings) > a:len
182
+    let fensave = &fen
183
+    let view = winsaveview()
184
+    call append(a:start + a:len - 1, repeat([''], len(a:strings) - a:len))
185
+    call winrestview(view)
186
+    let &fen = fensave
187
+  elseif len(a:strings) < a:len
188
+    let fensave = &fen
189
+    let view = winsaveview()
190
+    sil exe (a:start + len(a:strings)) . ',' .  (a:start + a:len - 1) . 'd_'
191
+    call winrestview(view)
192
+    let &fen = fensave
193
+  endif
194
+
195
+  call setline(a:start, a:strings)
196
+endfunction
197
+
198
+" Runs the given commandstring argument as an expression.                 {{{2
199
+" The commandstring expression is expected to reference the a:lines argument.
200
+" If the commandstring expression returns a list the items of that list will
201
+" replace the items in a:lines, otherwise the expression is assumed to have
202
+" modified a:lines itself.
203
+function! s:FilterString(lines, commandstring)
204
+  exe 'let rv = ' . a:commandstring
205
+
206
+  if type(rv) == type(a:lines) && rv isnot a:lines
207
+    call filter(a:lines, 0)
208
+    call extend(a:lines, rv)
209
+  endif
210
+endfunction
211
+
212
+" Public API                                                              {{{1
213
+
214
+if !exists("g:tabular_default_format")
215
+  let g:tabular_default_format = "l1"
216
+endif
217
+
218
+let s:formatelempat = '\%([lrc]\d\+\)'
219
+
220
+function! tabular#ElementFormatPattern()
221
+  return s:formatelempat
222
+endfunction
223
+
224
+" Given a list of strings and a delimiter, split each string on every
225
+" occurrence of the delimiter pattern, format each element according to either
226
+" the provided format (optional) or the default format, and join them back
227
+" together with enough space padding to guarantee that the nth delimiter of
228
+" each string is aligned.
229
+function! tabular#TabularizeStrings(strings, delim, ...)
230
+  if a:0 > 1
231
+    echoerr "TabularizeStrings accepts only 2 or 3 arguments (got ".(a:0+2).")"
232
+    return 1
233
+  endif
234
+
235
+  let formatstr = (a:0 ? a:1 : g:tabular_default_format)
236
+
237
+  if formatstr !~? s:formatelempat . '\+'
238
+    echoerr "Tabular: Invalid format \"" . formatstr . "\" specified!"
239
+    return 1
240
+  endif
241
+
242
+  let format = split(formatstr, s:formatelempat . '\zs')
243
+
244
+  let lines = a:strings
245
+
246
+  call map(lines, 's:SplitDelim(v:val, a:delim)')
247
+
248
+  let first_fields = []
249
+
250
+  " Strip spaces from non-delimiters; spaces in delimiters must have been
251
+  " matched intentionally
252
+  for line in lines
253
+    if len(line) == 1 && s:do_gtabularize
254
+      continue " Leave non-matching lines unchanged for GTabularize
255
+    endif
256
+
257
+    call add(first_fields, line[0])
258
+
259
+    if len(line) >= 1
260
+      for i in range(0, len(line)-1, 2)
261
+        let line[i] = s:StripLeadingSpaces(s:StripTrailingSpaces(line[i]))
262
+      endfor
263
+    endif
264
+  endfor
265
+
266
+  let common_indent = s:LongestCommonIndent(first_fields)
267
+
268
+  " Find the max length of each field
269
+  let maxes = []
270
+  for line in lines
271
+    if len(line) == 1 && s:do_gtabularize
272
+      continue " non-matching lines don't affect field widths for GTabularize
273
+    endif
274
+
275
+    for i in range(len(line))
276
+      if i == len(maxes)
277
+        let maxes += [ s:Strlen(line[i]) ]
278
+      else
279
+        let maxes[i] = max( [ maxes[i], s:Strlen(line[i]) ] )
280
+      endif
281
+    endfor
282
+  endfor
283
+
284
+  " Concatenate the fields, according to the format pattern.
285
+  for idx in range(len(lines))
286
+    let line = lines[idx]
287
+
288
+    if len(line) == 1 && s:do_gtabularize
289
+      let lines[idx] = line[0] " GTabularize doesn't change non-matching lines
290
+      continue
291
+    endif
292
+
293
+    for i in range(len(line))
294
+      let how = format[i % len(format)][0]
295
+      let pad = format[i % len(format)][1:-1]
296
+
297
+      if how =~? 'l'
298
+        let field = s:Left(line[i], maxes[i])
299
+      elseif how =~? 'r'
300
+        let field = s:Right(line[i], maxes[i])
301
+      elseif how =~? 'c'
302
+        let field = s:Center(line[i], maxes[i])
303
+      endif
304
+
305
+      let line[i] = field . repeat(" ", pad)
306
+    endfor
307
+
308
+    let prefix = common_indent
309
+    if len(line) == 1 && s:do_gtabularize
310
+      " We didn't strip the indent in this case; nothing to put back.
311
+      let prefix = ''
312
+    endif
313
+
314
+    let lines[idx] = s:StripTrailingSpaces(prefix . join(line, ''))
315
+  endfor
316
+endfunction
317
+
318
+" Apply 0 or more filters, in sequence, to selected text in the buffer    {{{2
319
+" The lines to be filtered are determined as follows:
320
+"   If the function is called with a range containing multiple lines, then
321
+"     those lines will be used as the range.
322
+"   If the function is called with no range or with a range of 1 line, then
323
+"     if GTabularize mode is being used,
324
+"       the range will not be adjusted
325
+"     if "includepat" is not specified,
326
+"       that 1 line will be filtered,
327
+"     if "includepat" is specified and that line does not match it,
328
+"       no lines will be filtered
329
+"     if "includepat" is specified and that line does match it,
330
+"       all contiguous lines above and below the specified line matching the
331
+"       pattern will be filtered.
332
+"
333
+" The remaining arguments must each be a filter to apply to the text.
334
+" Each filter must either be a String evaluating to a function to be called.
335
+function! tabular#PipeRange(includepat, ...) range
336
+  exe a:firstline . ',' . a:lastline
337
+      \ . 'call tabular#PipeRangeWithOptions(a:includepat, a:000, {})'
338
+endfunction
339
+
340
+" Extended version of tabular#PipeRange, which
341
+" 1) Takes the list of filters as an explicit list rather than as varargs
342
+" 2) Supports passing a dictionary of options to control the routine.
343
+"    Currently, the only supported option is 'mode', which determines whether
344
+"    to behave as :Tabularize or as :GTabularize
345
+" This allows me to add new features here without breaking API compatibility
346
+" in the future.
347
+function! tabular#PipeRangeWithOptions(includepat, filterlist, options) range
348
+  let top = a:firstline
349
+  let bot = a:lastline
350
+
351
+  let s:do_gtabularize = (get(a:options, 'mode', '') ==# 'GTabularize')
352
+
353
+  if !s:do_gtabularize
354
+    " In the default mode, apply range extension logic
355
+    if a:includepat != '' && top == bot
356
+      if top < 0 || top > line('$') || getline(top) !~ a:includepat
357
+        return
358
+      endif
359
+      while top > 1 && getline(top-1) =~ a:includepat
360
+        let top -= 1
361
+      endwhile
362
+      while bot < line('$') && getline(bot+1) =~ a:includepat
363
+        let bot += 1
364
+      endwhile
365
+    endif
366
+  endif
367
+
368
+  let lines = map(range(top, bot), 'getline(v:val)')
369
+
370
+  for filter in a:filterlist
371
+    if type(filter) != type("")
372
+      echoerr "PipeRange: Bad filter: " . string(filter)
373
+    endif
374
+
375
+    call s:FilterString(lines, filter)
376
+
377
+    unlet filter
378
+  endfor
379
+
380
+  call s:SetLines(top, bot - top + 1, lines)
381
+endfunction
382
+
383
+" Part of the public interface so interested pipelines can query this and
384
+" adjust their behavior appropriately.
385
+function! tabular#DoGTabularize()
386
+  return s:do_gtabularize
387
+endfunction
388
+
389
+function! s:SplitDelimTest(string, delim, expected)
390
+  let result = s:SplitDelim(a:string, a:delim)
391
+
392
+  if result !=# a:expected
393
+    echomsg 'Test failed!'
394
+    echomsg '  string=' . string(a:string) . '  delim=' . string(a:delim)
395
+    echomsg '  Returned=' . string(result)
396
+    echomsg '  Expected=' . string(a:expected)
397
+  endif
398
+endfunction
399
+
400
+function! tabular#SplitDelimUnitTest()
401
+  let assignment = '[|&+*/%<>=!~-]\@<!\([<>!=]=\|=\~\)\@![|&+*/%<>=!~-]*='
402
+  let two_spaces = '  '
403
+  let ternary_operator = '^.\{-}\zs?\|:'
404
+  let cpp_io = '<<\|>>'
405
+  let pascal_assign = ':='
406
+  let trailing_c_comments = '\/\*\|\*\/\|\/\/'
407
+
408
+  call s:SplitDelimTest('a+=b',    assignment, ['a', '+=', 'b'])
409
+  call s:SplitDelimTest('a-=b',    assignment, ['a', '-=', 'b'])
410
+  call s:SplitDelimTest('a!=b',    assignment, ['a!=b'])
411
+  call s:SplitDelimTest('a==b',    assignment, ['a==b'])
412
+  call s:SplitDelimTest('a&=b',    assignment, ['a', '&=', 'b'])
413
+  call s:SplitDelimTest('a|=b',    assignment, ['a', '|=', 'b'])
414
+  call s:SplitDelimTest('a=b=c',   assignment, ['a', '=', 'b', '=', 'c'])
415
+
416
+  call s:SplitDelimTest('a  b  c', two_spaces, ['a', '  ', 'b', '  ', 'c'])
417
+  call s:SplitDelimTest('a b   c', two_spaces, ['a b', '  ', ' c'])
418
+  call s:SplitDelimTest('ab    c', two_spaces, ['ab', '  ', '', '  ', 'c'])
419
+
420
+  call s:SplitDelimTest('a?b:c',   ternary_operator, ['a', '?', 'b', ':', 'c'])
421
+
422
+  call s:SplitDelimTest('a<<b<<c', cpp_io, ['a', '<<', 'b', '<<', 'c'])
423
+
424
+  call s:SplitDelimTest('a:=b=c',  pascal_assign, ['a', ':=', 'b=c'])
425
+
426
+  call s:SplitDelimTest('x//foo',  trailing_c_comments, ['x', '//', 'foo'])
427
+  call s:SplitDelimTest('x/*foo*/',trailing_c_comments, ['x', '/*', 'foo', '*/', ''])
428
+
429
+  call s:SplitDelimTest('#ab#cd#ef', '[^#]*', ['#', 'ab', '#', 'cd', '#', 'ef', ''])
430
+  call s:SplitDelimTest('#ab#cd#ef', '#\zs',  ['#', '', 'ab#', '', 'cd#', '', 'ef'])
431
+endfunction
432
+
433
+" Stupid vimscript crap, part 2                                           {{{1
434
+let &cpo = s:savecpo
435
+unlet s:savecpo
436
+
437
+" vim:set sw=2 sts=2 fdm=marker:
... ...
@@ -0,0 +1,260 @@
1
+*Tabular.txt*   Configurable, flexible, intuitive text aligning
2
+
3
+                                                       *tabular* *tabular.vim*
4
+
5
+       #|#|#|#|#|          #|                  #|                     ~
6
+           #|      #|#|#|  #|#|#|    #|    #|  #|    #|#|#|  #|  #|#| ~
7
+           #|    #|    #|  #|    #|  #|    #|  #|  #|    #|  #|#|     ~
8
+           #|    #|    #|  #|    #|  #|    #|  #|  #|    #|  #|       ~
9
+           #|      #|#|#|  #|#|#|      #|#|#|  #|    #|#|#|  #|       ~
10
+
11
+                                                  For Vim version 7.0 or newer
12
+
13
+                               By Matt Wozniski
14
+                                mjw@drexel.edu
15
+
16
+                               Reference Manual ~
17
+
18
+                                                                 *tabular-toc*
19
+
20
+1. Description                                           |tabular-intro|
21
+2. Walkthrough                                           |tabular-walkthrough|
22
+3. Scripting                                             |tabular-scripting|
23
+
24
+The functionality mentioned here is a plugin, see |add-plugin|.
25
+You can avoid loading this plugin by setting the "Tabular_loaded" global
26
+variable in your |vimrc| file: >
27
+    :let g:tabular_loaded = 1
28
+
29
+==============================================================================
30
+1. Description                                                 *tabular-intro*
31
+
32
+Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
33
+computer do this for you, since aligning things by hand quickly becomes
34
+unpleasant.  While there are other plugins for aligning text, the ones I've
35
+tried are either impossibly difficult to understand and use, or too simplistic
36
+to handle complicated tasks.  This plugin aims to make the easy things easy
37
+and the hard things possible, without providing an unnecessarily obtuse
38
+interface.  It's still a work in progress, and criticisms are welcome.
39
+
40
+==============================================================================
41
+2. Walkthrough                             *tabular-walkthrough* *:Tabularize*
42
+
43
+Tabular's commands are based largely on regular expressions.  The basic
44
+technique used by Tabular is taking some regex to match field delimiters,
45
+splitting the input lines at those delimiters, trimming unnecessary spaces
46
+from the non-delimiter parts, padding the non-delimiter parts of the lines
47
+with spaces to make them the same length, and joining things back together
48
+again.
49
+
50
+For instance, consider starting with the following lines:
51
+>
52
+    Some short phrase,some other phrase
53
+    A much longer phrase here,and another long phrase
54
+<
55
+Let's say we want to line these lines up at the commas.  We can tell
56
+Tabularize to do this by passing a pattern matching , to the Tabularize
57
+command:
58
+>
59
+  :Tabularize /,
60
+
61
+    Some short phrase         , some other phrase
62
+    A much longer phrase here , and another long phrase
63
+<
64
+I encourage you to try copying those lines to another buffer and trying to
65
+call :Tabularize.  You'll want to take notice of two things quickly: First,
66
+instead of requiring a range, Tabularize tries to figure out what you want to
67
+happen.  Since it knows that you want to act on lines matching a comma, it
68
+will look upwards and downwards for lines around the current line that match a
69
+comma, and consider all contiguous lines matching the pattern to be the range
70
+to be acted upon.  You can always override this by specifying a range, though.
71
+
72
+The second thing you should notice is that you'll almost certainly be able to
73
+abbreviate :Tabularize to :Tab - using this form in mappings and scripts is
74
+discouraged as it will make conflicts with other scripts more likely, but for
75
+interactive use it's a nice timesaver.  Another convenience feature is that
76
+running :Tabularize without providing a new pattern will cause it to reuse the
77
+last pattern it was called with.
78
+
79
+So, anyway, now the commas line up.  Splitting the lines on commas, Tabular
80
+realized that 'Some short phrase' would need to be padded with spaces to match
81
+the length of 'A much longer phrase here', and it did that before joining the
82
+lines back together.  You'll also notice that, in addition to the spaces
83
+inserting for padding, extra spaces were inserted between fields.  That's
84
+because by default, Tabular prints things left-aligned with one space between
85
+fields.  If you wanted to print things right-aligned with no spaces between
86
+fields, you would provide a different format to the Tabularize command:
87
+>
88
+  :Tabularize /,/r0
89
+
90
+            Some short phrase,      some other phrase
91
+    A much longer phrase here,and another long phrase
92
+<
93
+A format specifier is either l, r, or c, followed by one or more digits.  If
94
+the letter is l, the field will be left aligned, similarly for r and right
95
+aligning and c and center aligning.  The number following the letter is the
96
+number of spaces padding to insert before the start of the next field.
97
+Multiple format specifiers can be added to the same command - each field will
98
+be printed with the next format specifier in the list; when they all have been
99
+used the first will be used again, and so on.  So, the last command right
100
+aligned every field, then inserted 0 spaces of padding before the next field.
101
+What if we wanted to right align the text before the comma, and left align the
102
+text after the comma?  The command would look like this:
103
+>
104
+  :Tabularize /,/r1c1l0
105
+
106
+            Some short phrase , some other phrase
107
+    A much longer phrase here , and another long phrase
108
+<
109
+That command would be read as "Align the matching text, splitting fields on
110
+commas.  Print everything before the first comma right aligned, then 1 space,
111
+then the comma center aligned, then 1 space, then everything after the comma
112
+left aligned."  Notice that the alignment of the field the comma is in is
113
+irrelevant - since it's only 1 cell wide, it looks the same whether it's right,
114
+left, or center aligned.  Also notice that the 0 padding spaces specified for
115
+the 3rd field are unused - but they would be used if there were enough fields
116
+to require looping through the fields again.  For instance:
117
+>
118
+    abc,def,ghi
119
+    a,b
120
+    a,b,c
121
+
122
+  :Tabularize /,/r1c1l0
123
+
124
+    abc , def, ghi
125
+      a , b
126
+      a , b  ,  c
127
+<
128
+Notice that now, the format pattern has been reused; field 4 (the second comma)
129
+is right aligned, field 5 is center aligned.  No spaces were inserted between
130
+the 3rd field (containing "def") and the 4th field (the second comma) because
131
+the format specified 'l0'.
132
+
133
+But, what if you only wanted to act on the first comma on the line, rather than
134
+all of the commas on the line?  Let's say we want everything before the first
135
+comma right aligned, then the comma, then everything after the comma left
136
+aligned:
137
+>
138
+    abc,def,ghi
139
+    a,b
140
+    a,b,c
141
+
142
+  :Tabularize /^[^,]*\zs,/r0c0l0
143
+
144
+    abc,def,ghi
145
+      a,b
146
+      a,b,c
147
+<
148
+Here, we used a Vim regex that would only match the first comma on the line.
149
+It matches the beginning of the line, followed by all the non-comma characters
150
+up to the first comma, and then forgets about what it matched so far and
151
+pretends that the match starts exactly at the comma.
152
+
153
+But, now that this command does exactly what we want it to, it's become pretty
154
+unwieldy.  It would be unpleasant to need to type that more than once or
155
+twice.  The solution is to assign a name to it.
156
+>
157
+  :AddTabularPattern first_comma /^[^,]*\zs,/r0c0l0
158
+<
159
+Now, typing ":Tabularize first_comma" will do the same thing as typing the
160
+whole pattern out each time.  Of course this is more useful if you store the
161
+name in a file to be used later.
162
+
163
+NOTE: In order to make these new commands available every time vim starts,
164
+you'll need to put those new commands into a .vim file in a plugin directory
165
+somewhere in your 'runtimepath'.  In order to make sure that Tabular.vim has
166
+already been loaded before your file tries to use :AddTabularPattern or
167
+:AddTabularPipeline, the new file should be installed in an after/plugin
168
+directory in 'runtimepath'.  In general, it will be safe to find out where the
169
+TabularMaps.vim plugin was installed, and place other files extending
170
+Tabular.vim in the same directory as TabularMaps.vim.  For more information,
171
+and some suggested best practices, check out the |tabular-scripting| section.
172
+
173
+Lastly, we'll approach the case where tabular cannot achieve your desired goal
174
+just by splitting lines apart, trimming whitespace, padding with whitespace,
175
+and rejoining the lines.  As an example, consider the multiple_spaces command
176
+from TabularMaps.vim.  The goal is to split using two or more spaces as a
177
+field delimiter, and join fields back together, properly lined up, with only
178
+two spaces between the end of each field and the beginning of the next.
179
+Unfortunately, Tabular can't do this with only the commands we know so far:
180
+>
181
+  :Tabularize /  /
182
+<
183
+The above function won't work, because it will consider "a    b" as 5 fields
184
+delimited by two pairs of 2 spaces ( 'a', '  ', '', '  ', 'b' ) instead of as
185
+3 fields delimited by one set of 2 or more spaces ( 'a', '    ', 'b' ).
186
+>
187
+  :Tabularize /  \+/
188
+<
189
+The above function won't work either, because it will leave the delimiter as 4
190
+spaces when used against "a    b", meaning that we would fail at our goal of
191
+collapsing everything down to two spaces between fields.  So, we need a new
192
+command to get around this:
193
+>
194
+  :AddTabularPipeline multiple_spaces / \{2,}/
195
+    \ map(a:lines, "substitute(v:val, ' \{2,}', '  ', 'g')")
196
+    \   | tabular#TabularizeStrings(a:lines, '  ', 'l0')
197
+<
198
+Yeah.  I know it looks complicated.  Bear with me.  I probably will try to add
199
+in some shortcuts for this syntax, but this verbose will be guaranteed to
200
+always work.
201
+
202
+You should already recognize the name being assigned.  The next thing to
203
+happen is / \{2,}/ which is a pattern specifying which lines should
204
+automatically be included in the range when no range is given.  Without this,
205
+there would be no pattern to use for extending the range.  Everything after
206
+that is a | separated list of expressions to be evaluated.  In the context in
207
+which they will be evaluated, a:lines will be set to a List of Strings
208
+containing the text of the lines being filtered as they procede through the
209
+pipeline you've set up.  The \ at the start of the lines are just vim's line
210
+continuation marker; you needn't worry much about them.  So, the first
211
+expression in the pipeline transforms each line by replacing every instance of
212
+2 or more spaces with exactly two spaces.  The second command in the pipeline
213
+performs the equivalent of ":Tabularize /  /l0"; the only difference is that
214
+it is operating on a List of Strings rather than text in the buffer.  At the
215
+end of the pipeline, the Strings in the modified a:lines (or the return value
216
+of the last expression in the pipeline, if it returns a List) will replace the
217
+chosen range.
218
+
219
+==============================================================================
220
+3. Extending                                               *tabular-scripting*
221
+
222
+As mentioned above, the most important consideration when extending Tabular
223
+with new maps or commands is that your plugin must be loaded after Tabular.vim
224
+has finished loading, and only if Tabular.vim has loaded successfully.  The
225
+easiest approach to making sure it loads after Tabular.vim is simply putting
226
+the new file (we'll call it "tabular_extra.vim" as an example) into an
227
+"after/plugin/" directory in 'runtimepath', for instance:
228
+>
229
+  ~/.vim/after/plugin/tabular_extra.vim
230
+<
231
+The default set of mappings, found in "TabularMaps.vim", is installed in
232
+the after/plugin/ subdirectory of whatever directory Tabular was installed to.
233
+
234
+The other important consideration is making sure that your commands are only
235
+called if Tabular.vim was actually loaded.  The easiest way to do this is by
236
+checking for the existence of the :Tabularize command at the start of your
237
+plugin.  A short example plugin would look like this:
238
+>
239
+  " after/plugin/my_tabular_commands.vim
240
+  " Provides extra :Tabularize commands
241
+
242
+  if !exists(':Tabularize')
243
+    finish " Give up here; the Tabular plugin musn't have been loaded
244
+  endif
245
+
246
+  " Make line wrapping possible by resetting the 'cpo' option, first saving it
247
+  let s:save_cpo = &cpo
248
+  set cpo&vim
249
+
250
+  AddTabularPattern! asterisk /*/l1
251
+
252
+  AddTabularPipeline! remove_leading_spaces /^ /
253
+                  \ map(a:lines, "substitute(v:val, '^ *', '', '')")
254
+
255
+  " Restore the saved value of 'cpo'
256
+  let &cpo = s:save_cpo
257
+  unlet s:save_cpo
258
+<
259
+==============================================================================
260
+vim:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\":ts=8:ft=help:norl:
... ...
@@ -0,0 +1,350 @@
1
+" Tabular:     Align columnar data using regex-designated column boundaries
2
+" Maintainer:  Matthew Wozniski (godlygeek@gmail.com)
3
+" Date:        Thu, 03 May 2012 20:49:32 -0400
4
+" Version:     1.0
5
+"
6
+" Long Description:
7
+" Sometimes, it's useful to line up text.  Naturally, it's nicer to have the
8
+" computer do this for you, since aligning things by hand quickly becomes
9
+" unpleasant.  While there are other plugins for aligning text, the ones I've
10
+" tried are either impossibly difficult to understand and use, or too simplistic
11
+" to handle complicated tasks.  This plugin aims to make the easy things easy
12
+" and the hard things possible, without providing an unnecessarily obtuse
13
+" interface.  It's still a work in progress, and criticisms are welcome.
14
+"
15
+" License:
16
+" Copyright (c) 2012, Matthew J. Wozniski
17
+" All rights reserved.
18
+"
19
+" Redistribution and use in source and binary forms, with or without
20
+" modification, are permitted provided that the following conditions are met:
21
+"     * Redistributions of source code must retain the above copyright notice,
22
+"       this list of conditions and the following disclaimer.
23
+"     * Redistributions in binary form must reproduce the above copyright
24
+"       notice, this list of conditions and the following disclaimer in the
25
+"       documentation and/or other materials provided with the distribution.
26
+"     * The names of the contributors may not be used to endorse or promote
27
+"       products derived from this software without specific prior written
28
+"       permission.
29
+"
30
+" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
31
+" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
+" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
33
+" NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
34
+" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35
+" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36
+" OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37
+" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38
+" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39
+" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
+
41
+" Abort if running in vi-compatible mode or the user doesn't want us.
42
+if &cp || exists('g:tabular_loaded')
43
+  if &cp && &verbose
44
+    echo "Not loading Tabular in compatible mode."
45
+  endif
46
+  finish
47
+endif
48
+
49
+let g:tabular_loaded = 1
50
+
51
+" Stupid vimscript crap                                                   {{{1
52
+let s:savecpo = &cpo
53
+set cpo&vim
54
+
55
+" Private Things                                                          {{{1
56
+
57
+" Dictionary of command name to command
58
+let s:TabularCommands = {}
59
+
60
+" Generate tab completion list for :Tabularize                            {{{2
61
+" Return a list of commands that match the command line typed so far.
62
+" NOTE: Tries to handle commands with spaces in the name, but Vim doesn't seem
63
+"       to handle that terribly well... maybe I should give up on that.
64
+function! s:CompleteTabularizeCommand(argstart, cmdline, cursorpos)
65
+  let names = keys(s:TabularCommands)
66
+  if exists("b:TabularCommands")
67
+    let names += keys(b:TabularCommands)
68
+  endif
69
+
70
+  let cmdstart = substitute(a:cmdline, '^\s*\S\+\s*', '', '')
71
+
72
+  return filter(names, 'v:val =~# ''^\V'' . escape(cmdstart, ''\'')')
73
+endfunction
74
+
75
+" Choose the proper command map from the given command line               {{{2
76
+" Returns [ command map, command line with leading <buffer> removed ]
77
+function! s:ChooseCommandMap(commandline)
78
+  let map = s:TabularCommands
79
+  let cmd = a:commandline
80
+
81
+  if cmd =~# '^<buffer>\s\+'
82
+    if !exists('b:TabularCommands')
83
+      let b:TabularCommands = {}
84
+    endif
85
+    let map = b:TabularCommands
86
+    let cmd = substitute(cmd, '^<buffer>\s\+', '', '')
87
+  endif
88
+
89
+  return [ map, cmd ]
90
+endfunction
91
+
92
+" Parse '/pattern/format' into separate pattern and format parts.         {{{2
93
+" If parsing fails, return [ '', '' ]
94
+function! s:ParsePattern(string)
95
+  if a:string[0] != '/'
96
+    return ['','']
97
+  endif
98
+
99
+  let pat = '\\\@<!\%(\\\\\)\{-}\zs/' . tabular#ElementFormatPattern() . '*$'
100
+  let format = matchstr(a:string[1:-1], pat)
101
+  if !empty(format)
102
+    let format = format[1 : -1]
103
+    let pattern = a:string[1 : -len(format) - 2]
104
+  else
105
+    let pattern = a:string[1 : -1]
106
+  endif
107
+
108
+  return [pattern, format]
109
+endfunction
110
+
111
+" Split apart a list of | separated expressions.                          {{{2
112
+function! s:SplitCommands(string)
113
+  if a:string =~ '^\s*$'
114
+    return []
115
+  endif
116
+
117
+  let end = match(a:string, "[\"'|]")
118
+
119
+  " Loop until we find a delimiting | or end-of-string
120
+  while end != -1 && (a:string[end] != '|' || a:string[end+1] == '|')
121
+    if a:string[end] == "'"
122
+      let end = match(a:string, "'", end+1) + 1
123
+      if end == 0
124
+        throw "No matching end single quote"
125
+      endif
126
+    elseif a:string[end] == '"'
127
+      " Find a " preceded by an even number of \ (or 0)
128
+      let pattern = '\%(\\\@<!\%(\\\\\)*\)\@<="'
129
+      let end = matchend(a:string, pattern, end+1) + 1
130
+      if end == 0
131
+        throw "No matching end double quote"
132
+      endif
133
+    else " Found ||
134
+      let end += 2
135
+    endif
136
+
137
+    let end = match(a:string, "[\"'|]", end)
138
+  endwhile
139
+
140
+  if end == 0 || a:string[0 : end - (end > 0)] =~ '^\s*$'
141
+    throw "Empty element"
142
+  endif
143
+
144
+  if end == -1
145
+    let rv = [ a:string ]
146
+  else
147
+    let rv = [ a:string[0 : end-1] ] + s:SplitCommands(a:string[end+1 : -1])
148
+  endif
149
+
150
+  return rv
151
+endfunction
152
+
153
+" Public Things                                                           {{{1
154
+
155
+" Command associating a command name with a simple pattern command        {{{2
156
+" AddTabularPattern[!] [<buffer>] name /pattern[/format]
157
+"
158
+" If <buffer> is provided, the command will only be available in the current
159
+" buffer, and will be used instead of any global command with the same name.
160
+"
161
+" If a command with the same name and scope already exists, it is an error,
162
+" unless the ! is provided, in which case the existing command will be
163
+" replaced.
164
+"
165
+" pattern is a regex describing the delimiter to be used.
166
+"
167
+" format describes the format pattern to be used.  The default will be used if
168
+" none is provided.
169
+com! -nargs=+ -bang AddTabularPattern
170
+   \ call AddTabularPattern(<q-args>, <bang>0)
171
+
172
+function! AddTabularPattern(command, force)
173
+  try
174
+    let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
175
+
176
+    let name = matchstr(rest, '.\{-}\ze\s*/')
177
+    let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
178
+
179
+    let [ pattern, format ] = s:ParsePattern(pattern)
180
+
181
+    if empty(name) || empty(pattern)
182
+      throw "Invalid arguments!"
183
+    endif
184
+
185
+    if !a:force && has_key(commandmap, name)
186
+      throw string(name) . " is already defined, use ! to overwrite."
187
+    endif
188
+
189
+    let command = "tabular#TabularizeStrings(a:lines, " . string(pattern)
190
+
191
+    if !empty(format)
192
+      let command .=  ", " . string(format)
193
+    endif
194
+
195
+    let command .= ")"
196
+
197
+    let commandmap[name] = { 'pattern' : pattern, 'commands' : [ command ] }
198
+  catch
199
+    echohl ErrorMsg
200
+    echomsg "AddTabularPattern: " . v:exception
201
+    echohl None
202
+  endtry
203
+endfunction
204
+
205
+" Command associating a command name with a pipeline of functions         {{{2
206
+" AddTabularPipeline[!] [<buffer>] name /pattern/ func [ | func2 [ | func3 ] ]
207
+"
208
+" If <buffer> is provided, the command will only be available in the current
209
+" buffer, and will be used instead of any global command with the same name.
210
+"
211
+" If a command with the same name and scope already exists, it is an error,
212
+" unless the ! is provided, in which case the existing command will be
213
+" replaced.
214
+"
215
+" pattern is a regex that will be used to determine which lines will be
216
+" filtered.  If the cursor line doesn't match the pattern, using the command
217
+" will be a no-op, otherwise the cursor and all contiguous lines matching the
218
+" pattern will be filtered.
219
+"
220
+" Each 'func' argument represents a function to be called.  This function
221
+" will have access to a:lines, a List containing one String per line being
222
+" filtered.
223
+com! -nargs=+ -bang AddTabularPipeline
224
+   \ call AddTabularPipeline(<q-args>, <bang>0)
225
+
226
+function! AddTabularPipeline(command, force)
227
+  try
228
+    let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
229
+
230
+    let name = matchstr(rest, '.\{-}\ze\s*/')
231
+    let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
232
+
233
+    let commands = matchstr(pattern, '^/.\{-}\\\@<!\%(\\\\\)\{-}/\zs.*')
234
+    let pattern = matchstr(pattern, '/\zs.\{-}\\\@<!\%(\\\\\)\{-}\ze/')
235
+
236
+    if empty(name) || empty(pattern)
237
+      throw "Invalid arguments!"
238
+    endif
239
+
240
+    if !a:force && has_key(commandmap, name)
241
+      throw string(name) . " is already defined, use ! to overwrite."
242
+    endif
243
+
244
+    let commandlist = s:SplitCommands(commands)
245
+
246
+    if empty(commandlist)
247
+      throw "Must provide a list of functions!"
248
+    endif
249
+
250
+    let commandmap[name] = { 'pattern' : pattern, 'commands' : commandlist }
251
+  catch
252
+    echohl ErrorMsg
253
+    echomsg "AddTabularPipeline: " . v:exception
254
+    echohl None
255
+  endtry
256
+endfunction
257
+
258
+" Tabularize /pattern[/format]                                            {{{2
259
+" Tabularize name
260
+"
261
+" Align text, either using the given pattern, or the command associated with
262
+" the given name.
263
+com! -nargs=* -range -complete=customlist,<SID>CompleteTabularizeCommand
264
+   \ Tabularize <line1>,<line2>call Tabularize(<q-args>)
265
+
266
+function! Tabularize(command, ...) range
267
+  let piperange_opt = {}
268
+  if a:0
269
+    let piperange_opt = a:1
270
+  endif
271
+
272
+  if empty(a:command)
273
+    if !exists("s:last_tabularize_command")
274
+      echohl ErrorMsg
275
+      echomsg "Tabularize hasn't been called yet; no pattern/command to reuse!"
276
+      echohl None
277
+      return
278
+    endif
279
+  else
280
+    let s:last_tabularize_command = a:command
281
+  endif
282
+
283
+  let command = s:last_tabularize_command
284
+
285
+  let range = a:firstline . ',' . a:lastline
286
+
287
+  try
288
+    let [ pattern, format ] = s:ParsePattern(command)
289
+
290
+    if !empty(pattern)
291
+      let cmd  = "tabular#TabularizeStrings(a:lines, " . string(pattern)
292
+
293
+      if !empty(format)
294
+        let cmd .= "," . string(format)
295
+      endif
296
+
297
+      let cmd .= ")"
298
+
299
+      exe range . 'call tabular#PipeRangeWithOptions(pattern, [ cmd ], '
300
+                      \ . 'piperange_opt)'
301
+    else
302
+      if exists('b:TabularCommands') && has_key(b:TabularCommands, command)
303
+        let usercmd = b:TabularCommands[command]
304
+      elseif has_key(s:TabularCommands, command)
305
+        let usercmd = s:TabularCommands[command]
306
+      else
307
+        throw "Unrecognized command " . string(command)
308
+      endif
309
+
310
+      exe range . 'call tabular#PipeRangeWithOptions(usercmd["pattern"], '
311
+                      \ . 'usercmd["commands"], piperange_opt)'
312
+    endif
313
+  catch
314
+    echohl ErrorMsg
315
+    echomsg "Tabularize: " . v:exception
316
+    echohl None
317
+    return
318
+  endtry
319
+endfunction
320
+
321
+function! TabularizeHasPattern()
322
+  return exists("s:last_tabularize_command")
323
+endfunction
324
+
325
+" GTabularize /pattern[/format]                                           {{{2
326
+" GTabularize name
327
+"
328
+" Align text on only matching lines, either using the given pattern, or the
329
+" command associated with the given name.  Mnemonically, this is similar to
330
+" the :global command, which takes some action on all rows matching a pattern
331
+" in a range.  This command is different from normal :Tabularize in 3 ways:
332
+"   1) If a line in the range does not match the pattern, it will be left
333
+"      unchanged, and not in any way affect the outcome of other lines in the
334
+"      range (at least, normally - but Pipelines can and will still look at
335
+"      non-matching rows unless they are specifically written to be aware of
336
+"      tabular#DoGTabularize() and handle it appropriately).
337
+"   2) No automatic range determination - :Tabularize automatically expands
338
+"      a single-line range (or a call with no range) to include all adjacent
339
+"      matching lines.  That behavior does not make sense for this command.
340
+"   3) If called without a range, it will act on all lines in the buffer (like
341
+"      :global) rather than only a single line
342
+com! -nargs=* -range=% -complete=customlist,<SID>CompleteTabularizeCommand
343
+   \ GTabularize <line1>,<line2>
344
+   \ call Tabularize(<q-args>, { 'mode': 'GTabularize' } )
345
+
346
+" Stupid vimscript crap, part 2                                           {{{1
347
+let &cpo = s:savecpo
348
+unlet s:savecpo
349
+
350
+" vim:set sw=2 sts=2 fdm=marker:
... ...
@@ -63,7 +63,8 @@ See [config.dlma.com](http://config.dlma.com) for more.
63 63
         4. [visual-star-search](http://got-ravings.blogspot.com/2008/07/vim-pr0n-visual-search-mappings.html), so * and # work in visual mode too.
64 64
         5. [git-tab](https://github.com/dblume/gittab), use integrated context-sensitive git commands
65 65
         6. [rainbow](https://github.com/luochen1990/rainbow), for matching colored parentheses. 
66
-        7. Assorted favorite colors like [desert](https://github.com/dblume/desert.vim).
66
+        7. [tabular](https://github.com/godlygeek/tabular), for when aligning tables. 
67
+        8. Assorted favorite colors like [desert](https://github.com/dblume/desert.vim).
67 68
 3. Neovim resources
68 69
     1. .config/nvim/init.vim
69 70
     2. .config/nvim/colors/nvim\_desert.vim
70 71