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 |