summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.vim/after/plugin/TabularMaps.vim48
-rw-r--r--.vim/autoload/tabular.vim283
-rw-r--r--.vim/doc/Tabular.txt258
-rw-r--r--.vim/plugin/Tabular.vim280
4 files changed, 869 insertions, 0 deletions
diff --git a/.vim/after/plugin/TabularMaps.vim b/.vim/after/plugin/TabularMaps.vim
new file mode 100644
index 0000000..20f7141
--- /dev/null
+++ b/.vim/after/plugin/TabularMaps.vim
@@ -0,0 +1,48 @@
+if !exists(':Tabularize')
+ finish " Tabular.vim wasn't loaded
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+AddTabularPattern! assignment /[|&+*/%<>=!~-]\@<!\([<>!=]=\|=\~\)\@![|&+*/%<>=!~-]*=/l1r1
+AddTabularPattern! two_spaces / /l0
+
+AddTabularPipeline! multiple_spaces / / map(a:lines, "substitute(v:val, ' *', ' ', 'g')") | tabular#TabularizeStrings(a:lines, ' ', 'l0')
+
+AddTabularPipeline! argument_list /(.*)/ map(a:lines, 'substitute(v:val, ''\s*\([(,)]\)\s*'', ''\1'', ''g'')')
+ \ | tabular#TabularizeStrings(a:lines, '[(,)]', 'l0')
+ \ | map(a:lines, 'substitute(v:val, ''\(\s*\),'', '',\1 '', "g")')
+ \ | map(a:lines, 'substitute(v:val, ''\s*)'', ")", "g")')
+
+function! SplitCDeclarations(lines)
+ let rv = []
+ for line in a:lines
+ " split the line into declaractions
+ let split = split(line, '\s*[,;]\s*')
+ " separate the type from the first declaration
+ let type = substitute(split[0], '\%(\%([&*]\s*\)*\)\=\k\+$', '', '')
+ " add the ; back on every declaration
+ call map(split, 'v:val . ";"')
+ " add the first element to the return as-is, and remove it from the list
+ let rv += [ remove(split, 0) ]
+ " transform the other elements by adding the type on at the beginning
+ call map(split, 'type . v:val')
+ " and add them all to the return
+ let rv += split
+ endfor
+ return rv
+endfunction
+
+AddTabularPipeline! split_declarations /,.*;/ SplitCDeclarations(a:lines)
+
+AddTabularPattern! ternary_operator /^.\{-}\zs?\|:/l1
+
+AddTabularPattern! cpp_io /<<\|>>/l1
+
+AddTabularPattern! pascal_assign /:=/l1
+
+AddTabularPattern! trailing_c_comments /\/\*\|\*\/\|\/\//l1
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
diff --git a/.vim/autoload/tabular.vim b/.vim/autoload/tabular.vim
new file mode 100644
index 0000000..9ed6033
--- /dev/null
+++ b/.vim/autoload/tabular.vim
@@ -0,0 +1,283 @@
+" Tabular: Align columnar data using regex-designated column boundaries
+" Maintainer: Matthew Wozniski (mjw@drexel.edu)
+" Date: Thu, 11 Oct 2007 00:35:34 -0400
+" Version: 0.1
+
+" Stupid vimscript crap {{{1
+let s:savecpo = &cpo
+set cpo&vim
+
+" Private Functions {{{1
+
+" Return the number of bytes in a string after expanding tabs to spaces. {{{2
+" This expansion is done based on the current value of 'tabstop'
+function! s:Strlen(string)
+ let rv = 0
+ let i = 0
+
+ for char in split(a:string, '\zs')
+ if char == "\t"
+ let rv += &ts - i
+ let i = 0
+ else
+ let rv += 1
+ let i = (i + 1) % &ts
+ endif
+ endfor
+
+ return rv
+endfunction
+
+" Align a string within a field {{{2
+" These functions do not trim leading and trailing spaces.
+
+" Right align 'string' in a field of size 'fieldwidth'
+function! s:Right(string, fieldwidth)
+ let spaces = a:fieldwidth - s:Strlen(a:string)
+ return matchstr(a:string, '^\s*') . repeat(" ", spaces) . substitute(a:string, '^\s*', '', '')
+endfunction
+
+" Left align 'string' in a field of size 'fieldwidth'
+function! s:Left(string, fieldwidth)
+ let spaces = a:fieldwidth - s:Strlen(a:string)
+ return a:string . repeat(" ", spaces)
+endfunction
+
+" Center align 'string' in a field of size 'fieldwidth'
+function! s:Center(string, fieldwidth)
+ let spaces = a:fieldwidth - s:Strlen(a:string)
+ let right = spaces / 2
+ let left = right + (right * 2 != spaces)
+ return repeat(" ", left) . a:string . repeat(" ", right)
+endfunction
+
+" Remove spaces around a string {{{2
+
+" Remove all trailing spaces from a string.
+function! s:StripTrailingSpaces(string)
+ return matchstr(a:string, '^.\{-}\ze\s*$')
+endfunction
+
+" Remove all leading spaces from a string.
+function! s:StripLeadingSpaces(string)
+ return matchstr(a:string, '^\s*\zs.*$')
+endfunction
+
+" Split a string into fields and delimiters {{{2
+" Like split(), but include the delimiters as elements
+" All odd numbered elements are delimiters
+" All even numbered elements are non-delimiters (including zero)
+function! s:SplitDelim(string, delim)
+ let rv = []
+ let beg = 0
+ let idx = 0
+
+ let len = len(a:string)
+
+ while 1
+ let mid = match(a:string, a:delim, beg, 1)
+ if mid == -1 || mid == len
+ break
+ endif
+
+ let matchstr = matchstr(a:string, a:delim, beg, 1)
+ let length = strlen(matchstr)
+
+ if beg < mid
+ let rv += [ a:string[beg : mid-1] ]
+ else
+ let rv += [ "" ]
+ endif
+
+ let beg = mid + length
+ let idx = beg
+
+ if beg == mid
+ " Empty match, advance "beg" by one to avoid infinite loop
+ let rv += [ "" ]
+ let beg += 1
+ else " beg > mid
+ let rv += [ a:string[mid : beg-1] ]
+ endif
+ endwhile
+
+ let rv += [ strpart(a:string, idx) ]
+
+ return rv
+endfunction
+
+" Replace lines from `start' to `start + len - 1' with the given strings. {{{2
+" If more lines are needed to show all strings, they will be added.
+" If there are too few strings to fill all lines, lines will be removed.
+function! s:SetLines(start, len, strings)
+ if a:start > line('$') + 1 || a:start < 1
+ throw "Invalid start line!"
+ endif
+
+ if len(a:strings) > a:len
+ let fensave = &fen
+ let view = winsaveview()
+ call append(a:start + a:len - 1, repeat([''], len(a:strings) - a:len))
+ call winrestview(view)
+ let &fen = fensave
+ elseif len(a:strings) < a:len
+ let fensave = &fen
+ let view = winsaveview()
+ sil exe (a:start + len(a:strings)) . ',' . (a:start + a:len - 1) . 'd_'
+ call winrestview(view)
+ let &fen = fensave
+ endif
+
+ call setline(a:start, a:strings)
+endfunction
+
+" Runs the given commandstring argument as an expression. {{{2
+" The commandstring expression is expected to reference the a:lines argument.
+" If the commandstring expression returns a list the items of that list will
+" replace the items in a:lines, otherwise the expression is assumed to have
+" modified a:lines itself.
+function! s:FilterString(lines, commandstring)
+ exe 'let rv = ' . a:commandstring
+
+ if type(rv) == type(a:lines) && rv isnot a:lines
+ call filter(a:lines, 0)
+ call extend(a:lines, rv)
+ endif
+endfunction
+
+" Public API {{{1
+
+if !exists("g:tabular_default_format")
+ let g:tabular_default_format = "l1"
+endif
+
+let s:formatelempat = '\%([lrc]\d\+\)'
+
+function! tabular#ElementFormatPattern()
+ return s:formatelempat
+endfunction
+
+" Given a list of strings and a delimiter, split each string on every
+" occurrence of the delimiter pattern, format each element according to either
+" the provided format (optional) or the default format, and join them back
+" together with enough space padding to guarantee that the nth delimiter of
+" each string is aligned.
+function! tabular#TabularizeStrings(strings, delim, ...)
+ if a:0 > 1
+ echoerr "TabularizeStrings accepts only 2 or 3 arguments (got ".(a:0+2).")"
+ return 1
+ endif
+
+ let formatstr = (a:0 ? a:1 : g:tabular_default_format)
+
+ if formatstr !~? s:formatelempat . '\+'
+ echoerr "Tabular: Invalid format \"" . formatstr . "\" specified!"
+ return 1
+ endif
+
+ let format = split(formatstr, s:formatelempat . '\zs')
+
+ let lines = map(a:strings, 's:SplitDelim(v:val, a:delim)')
+
+ " Strip spaces
+ " - Only from non-delimiters; spaces in delimiters must have been matched
+ " intentionally
+ " - Don't strip leading spaces from the first element; we like indenting.
+ for line in lines
+ if line[0] !~ '^\s*$'
+ let line[0] = s:StripTrailingSpaces(line[0])
+ endif
+ if len(line) >= 3
+ for i in range(2, len(line)-1, 2)
+ let line[i] = s:StripLeadingSpaces(s:StripTrailingSpaces(line[i]))
+ endfor
+ endif
+ endfor
+
+ " Find the max length of each field
+ let maxes = []
+ for line in lines
+ for i in range(len(line))
+ if i == len(maxes)
+ let maxes += [ s:Strlen(line[i]) ]
+ else
+ let maxes[i] = max( [ maxes[i], s:Strlen(line[i]) ] )
+ endif
+ endfor
+ endfor
+
+ let lead_blank = empty(filter(copy(lines), 'v:val[0] =~ "\\S"'))
+
+ " Concatenate the fields, according to the format pattern.
+ for idx in range(len(lines))
+ let line = lines[idx]
+ for i in range(len(line))
+ let how = format[i % len(format)][0]
+ let pad = format[i % len(format)][1:-1]
+
+ if how =~? 'l'
+ let field = s:Left(line[i], maxes[i])
+ elseif how =~? 'r'
+ let field = s:Right(line[i], maxes[i])
+ elseif how =~? 'c'
+ let field = s:Center(line[i], maxes[i])
+ endif
+
+ let line[i] = field . (lead_blank && i == 0 ? '' : repeat(" ", pad))
+ endfor
+
+ let lines[idx] = s:StripTrailingSpaces(join(line, ''))
+ endfor
+endfunction
+
+" Apply 0 or more filters, in sequence, to selected text in the buffer {{{2
+" The lines to be filtered are determined as follows:
+" If the function is called with a range containing multiple lines, then
+" those lines will be used as the range.
+" If the function is called with no range or with a range of 1 line, then
+" if "includepat" is not specified,
+" that 1 line will be filtered,
+" if "includepat" is specified and that line does not match it,
+" no lines will be filtered
+" if "includepat" is specified and that line does match it,
+" all contiguous lines above and below the specified line matching the
+" pattern will be filtered.
+"
+" The remaining arguments must each be a filter to apply to the text.
+" Each filter must either be a String evaluating to a function to be called.
+function! tabular#PipeRange(includepat, ...) range
+ let top = a:firstline
+ let bot = a:lastline
+
+ if a:includepat != '' && top == bot
+ if top < 0 || top > line('$') || getline(top) !~ a:includepat
+ return
+ endif
+ while top > 1 && getline(top-1) =~ a:includepat
+ let top -= 1
+ endwhile
+ while bot < line('$') && getline(bot+1) =~ a:includepat
+ let bot += 1
+ endwhile
+ endif
+
+ let lines = map(range(top, bot), 'getline(v:val)')
+
+ for filter in a:000
+ if type(filter) != type("")
+ echoerr "PipeRange: Bad filter: " . string(filter)
+ endif
+
+ call s:FilterString(lines, filter)
+
+ unlet filter
+ endfor
+
+ call s:SetLines(top, bot - top + 1, lines)
+endfunction
+
+" Stupid vimscript crap, part 2 {{{1
+let &cpo = s:savecpo
+unlet s:savecpo
+
+" vim:set sw=2 sts=2 fdm=marker:
diff --git a/.vim/doc/Tabular.txt b/.vim/doc/Tabular.txt
new file mode 100644
index 0000000..34f4765
--- /dev/null
+++ b/.vim/doc/Tabular.txt
@@ -0,0 +1,258 @@
+*Tabular.txt* Configurable, flexible, intuitive text aligning
+
+ *tabular* *tabular.vim*
+
+ #|#|#|#|#| #| #| ~
+ #| #|#|#| #|#|#| #| #| #| #|#|#| #| #|#| ~
+ #| #| #| #| #| #| #| #| #| #| #|#| ~
+ #| #| #| #| #| #| #| #| #| #| #| ~
+ #| #|#|#| #|#|#| #|#|#| #| #|#|#| #| ~
+
+ For Vim version 7.0 or newer
+
+ By Matt Wozniski
+ mjw@drexel.edu
+
+ Reference Manual ~
+
+ *tabular-toc*
+
+1. Description |tabular-intro|
+2. Walkthrough |tabular-walkthrough|
+3. Scripting |tabular-scripting|
+
+The functionality mentioned here is a plugin, see |add-plugin|.
+You can avoid loading this plugin by setting the "Tabular_loaded" global
+variable in your |vimrc| file: >
+ :let g:tabular_loaded = 1
+
+==============================================================================
+1. Description *tabular-intro*
+
+Sometimes, it's useful to line up text. Naturally, it's nicer to have the
+computer do this for you, since aligning things by hand quickly becomes
+unpleasant. While there are other plugins for aligning text, the ones I've
+tried are either impossibly difficult to understand and use, or too simplistic
+to handle complicated tasks. This plugin aims to make the easy things easy
+and the hard things possible, without providing an unnecessarily obtuse
+interface. It's still a work in progress, and criticisms are welcome.
+
+==============================================================================
+2. Walkthrough *tabular-walkthrough*
+
+Tabular's commands are based largely on regular expressions. The basic
+technique used by Tabular is taking some regex to match field delimiters,
+splitting the input lines at those delimiters, trimming unnecessary spaces
+from the non-delimiter parts, padding the non-delimiter parts of the lines
+with spaces to make them the same length, and joining things back together
+again.
+
+For instance, consider starting with the following lines:
+>
+ Some short phrase,some other phrase
+ A much longer phrase here,and another long phrase
+<
+Let's say we want to line these lines up at the commas. We can tell
+Tabularize to do this by passing a pattern matching , to the Tabularize
+command:
+>
+ :Tabularize /,
+
+ Some short phrase , some other phrase
+ A much longer phrase here , and another long phrase
+<
+I encourage you to try copying those lines to another buffer and trying to
+call :Tabularize. You'll want to take notice of two things quickly: First,
+instead of requiring a range, Tabularize tries to figure out what you want to
+happen. Since it knows that you want to act on lines matching a comma, it
+will look upwards and downwards for lines around the current line that match a
+comma, and consider all contiguous lines matching the pattern to be the range
+to be acted upon. You can always override this by specifying a range, though.
+
+The second thing you should notice is that you'll almost certainly be able to
+abbreviate :Tabularize to :Tab - using this form in mappings and scripts is
+discouraged as it will make conflicts with other scripts more likely, but for
+interactive use it's a nice timesaver.
+
+So, anyway, now the commas line up. Splitting the lines on commas, Tabular
+realized that 'Some short phrase' would need to be padded with spaces to match
+the length of 'A much longer phrase here', and it did that before joining the
+lines back together. You'll also notice that, in addition to the spaces
+inserting for padding, extra spaces were inserted between fields. That's
+because by default, Tabular prints things left-aligned with one space between
+fields. If you wanted to print things right-aligned with no spaces between
+fields, you would provide a different format to the Tabularize command:
+>
+ :Tabularize /,/r0
+
+ Some short phrase, some other phrase
+ A much longer phrase here,and another long phrase
+<
+A format specifier is either l, r, or c, followed by one or more digits. If
+the letter is l, the field will be left aligned, similarly for r and right
+aligning and c and center aligning. The number following the letter is the
+number of spaces padding to insert before the start of the next field.
+Multiple format specifiers can be added to the same command - each field will
+be printed with the next format specifier in the list; when they all have been
+used the first will be used again, and so on. So, the last command right
+aligned every field, then inserted 0 spaces of padding before the next field.
+What if we wanted to right align the text before the comma, and left align the
+text after the comma? The command would look like this:
+>
+ :Tabularize /,/r1c1l0
+
+ Some short phrase , some other phrase
+ A much longer phrase here , and another long phrase
+<
+That command would be read as "Align the matching text, splitting fields on
+commas. Print everything before the first comma right aligned, then 1 space,
+then the comma center aligned, then 1 space, then everything after the comma
+left aligned." Notice that the alignment of the field the comma is in is
+irrelevant - since it's only 1 cell wide, it looks the same whether it's right,
+left, or center aligned. Also notice that the 0 padding spaces specified for
+the 3rd field are unused - but they would be used if there were enough fields
+to require looping through the fields again. For instance:
+>
+ abc,def,ghi
+ a,b
+ a,b,c
+
+ :Tabularize /,/r1c1l0
+
+ abc , def, ghi
+ a , b
+ a , b , c
+<
+Notice that now, the format pattern has been reused; field 4 (the second comma)
+is right aligned, field 5 is center aligned. No spaces were inserted between
+the 3rd field (containing "def") and the 4th field (the second comma) because
+the format specified 'l0'.
+
+But, what if you only wanted to act on the first comma on the line, rather than
+all of the commas on the line? Let's say we want everything before the first
+comma right aligned, then the comma, then everything after the comma left
+aligned:
+>
+ abc,def,ghi
+ a,b
+ a,b,c
+
+ :Tabularize /^[^,]*\zs,/r0c0l0
+
+ abc,def,ghi
+ a,b
+ a,b,c
+<
+Here, we used a Vim regex that would only match the first comma on the line.
+It matches the beginning of the line, followed by all the non-comma characters
+up to the first comma, and then forgets about what it matched so far and
+pretends that the match starts exactly at the comma.
+
+But, now that this command does exactly what we want it to, it's become pretty
+unwieldy. It would be unpleasant to need to type that more than once or
+twice. The solution is to assign a name to it.
+>
+ :AddTabularPattern first_comma /^[^,]*\zs,/r0c0l0
+<
+Now, typing ":Tabularize first_comma" will do the same thing as typing the
+whole pattern out each time. Of course this is more useful if you store the
+name in a file to be used later.
+
+NOTE: In order to make these new commands available every time vim starts,
+you'll need to put those new commands into a .vim file in a plugin directory
+somewhere in your 'runtimepath'. In order to make sure that Tabular.vim has
+already been loaded before your file tries to use :AddTabularPattern or
+:AddTabularPipeline, the new file should be installed in an after/plugin
+directory in 'runtimepath'. In general, it will be safe to find out where the
+TabularMaps.vim plugin was installed, and place other files extending
+Tabular.vim in the same directory as TabularMaps.vim. For more information,
+and some suggested best practices, check out the |tabular-scripting| section.
+
+Lastly, we'll approach the case where tabular cannot achieve your desired goal
+just by splitting lines appart, trimming whitespace, padding with whitespace,
+and rejoining the lines. As an example, consider the multiple_spaces command
+from TabularMaps.vim. The goal is to split using two or more spaces as a
+field delimiter, and join fields back together, properly lined up, with only
+two spaces between the end of each field and the beginning of the next.
+Unfortunately, Tabular can't do this with only the commands we know so far:
+>
+ :Tabularize / /
+<
+The above function won't work, because it will consider "a b" as 5 fields
+delimited by two pairs of 2 spaces ( 'a', ' ', '', ' ', 'b' ) instead of as
+3 fields delimited by one set of 2 or more spaces ( 'a', ' ', 'b' ).
+>
+ :Tabularize / \+/
+<
+The above function won't work either, because it will leave the delimiter as 4
+spaces when used against "a b", meaning that we would fail at our goal of
+collapsing everything down to two spaces between fields. So, we need a new
+command to get around this:
+>
+ :AddTabularPipeline multiple_spaces / \{2,}/
+ \ map(a:lines, "substitute(v:val, ' \{2,}', ' ', 'g')")
+ \ | tabular#TabularizeStrings(a:lines, ' ', 'l0')
+<
+Yeah. I know it looks complicated. Bear with me. I probably will try to add
+in some shortcuts for this syntax, but this verbose will be guaranteed to
+always work.
+
+You should already recognize the name being assigned. The next thing to
+happen is / \{2,}/ which is a pattern specifying which lines should
+automatically be included in the range when no range is given. Without this,
+there would be no pattern to use for extending the range. Everything after
+that is a | separated list of expressions to be evaluated. In the context in
+which they will be evaluated, a:lines will be set to a List of Strings
+containing the text of the lines being filtered as they procede through the
+pipeline you've set up. The \ at the start of the lines are just vim's line
+continuation marker; you needn't worry much about them. So, the first
+expression in the pipeline transforms each line by replacing every instance of
+2 or more spaces with exactly two spaces. The second command in the pipeline
+performs the equivalent of ":Tabularize / /l0"; the only difference is that
+it is operating on a List of Strings rather than text in the buffer. At the
+end of the pipeline, the Strings in the modified a:lines (or the return value
+of the last expression in the pipeline, if it returns a List) will replace the
+chosen range.
+
+==============================================================================
+3. Extending *tabular-scripting*
+
+As mentioned above, the most important consideration when extending Tabular
+with new maps or commands is that your plugin must be loaded after Tabular.vim
+has finished loading, and only if Tabular.vim has loaded successfully. The
+easiest approach to making sure it loads after Tabular.vim is simply putting
+the new file (we'll call it "tabular_extra.vim" as an example) into an
+"after/plugin/" directory in 'runtimepath', for instance:
+>
+ ~/.vim/after/plugin/tabular_extra.vim
+<
+The default set of mappings, found in "TabularMaps.vim", is installed in
+the after/plugin/ subdirectory of whatever directory Tabular was installed to.
+
+The other important consideration is making sure that your commands are only
+called if Tabular.vim was actually loaded. The easiest way to do this is by
+checking for the existence of the :Tabularize command at the start of your
+plugin. A short example plugin would look like this:
+>
+ " after/plugin/my_tabular_commands.vim
+ " Provides extra :Tabularize commands
+
+ if !exists(':Tabularize')
+ finish " Give up here; the Tabular plugin musn't have been loaded
+ endif
+
+ " Make line wrapping possible by resetting the 'cpo' option, first saving it
+ let s:save_cpo = &cpo
+ set cpo&vim
+
+ AddTabularPattern! asterisk /*/l1
+
+ AddTabularPipeline! remove_leading_spaces /^ /
+ \ map(a:lines, "substitute(v:val, '^ *', '', '')")
+
+ " Restore the saved value of 'cpo'
+ let &cpo = s:save_cpo
+ unlet s:save_cpo
+<
+==============================================================================
+vim:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\":ts=8:ft=help:norl:
diff --git a/.vim/plugin/Tabular.vim b/.vim/plugin/Tabular.vim
new file mode 100644
index 0000000..84e08a7
--- /dev/null
+++ b/.vim/plugin/Tabular.vim
@@ -0,0 +1,280 @@
+" Tabular: Align columnar data using regex-designated column boundaries
+" Maintainer: Matthew Wozniski (mjw@drexel.edu)
+" Date: Thu, 11 Oct 2007 00:35:34 -0400
+" Version: 0.1
+
+" Abort if running in vi-compatible mode or the user doesn't want us.
+if &cp || exists('g:tabular_loaded')
+ if &cp && &verbose
+ echo "Not loading Tabular in compatible mode."
+ endif
+ finish
+endif
+
+let g:tabular_loaded = 1
+
+" Stupid vimscript crap {{{1
+let s:savecpo = &cpo
+set cpo&vim
+
+" Private Things {{{1
+
+" Dictionary of command name to command
+let s:TabularCommands = {}
+
+" Generate tab completion list for :Tabularize {{{2
+" Return a list of commands that match the command line typed so far.
+" NOTE: Tries to handle commands with spaces in the name, but Vim doesn't seem
+" to handle that terribly well... maybe I should give up on that.
+function! s:CompleteTabularizeCommand(argstart, cmdline, cursorpos)
+ let names = keys(s:TabularCommands)
+ if exists("b:TabularCommands")
+ let names += keys(b:TabularCommands)
+ endif
+
+ let cmdstart = substitute(a:cmdline, '^\s*\S\+\s*', '', '')
+
+ return filter(names, 'v:val =~# ''^\V'' . escape(cmdstart, ''\'')')
+endfunction
+
+" Choose the proper command map from the given command line {{{2
+" Returns [ command map, command line with leading <buffer> removed ]
+function! s:ChooseCommandMap(commandline)
+ let map = s:TabularCommands
+ let cmd = a:commandline
+
+ if cmd =~# '^<buffer>\s\+'
+ if !exists('b:TabularCommands')
+ let b:TabularCommands = {}
+ endif
+ let map = b:TabularCommands
+ let cmd = substitute(cmd, '^<buffer>\s\+', '', '')
+ endif
+
+ return [ map, cmd ]
+endfunction
+
+" Parse '/pattern/format' into separate pattern and format parts. {{{2
+" If parsing fails, return [ '', '' ]
+function! s:ParsePattern(string)
+ if a:string[0] != '/'
+ return ['','']
+ endif
+
+ let pat = '\\\@<!\%(\\\\\)\{-}\zs/' . tabular#ElementFormatPattern() . '*$'
+ let format = matchstr(a:string[1:-1], pat)
+ if !empty(format)
+ let format = format[1 : -1]
+ let pattern = a:string[1 : -len(format) - 2]
+ else
+ let pattern = a:string[1 : -1]
+ endif
+
+ return [pattern, format]
+endfunction
+
+" Split apart a list of | separated expressions. {{{2
+function! s:SplitCommands(string)
+ if a:string =~ '^\s*$'
+ return []
+ endif
+
+ let end = match(a:string, "[\"'|]")
+
+ " Loop until we find a delimiting | or end-of-string
+ while end != -1 && (a:string[end] != '|' || a:string[end+1] == '|')
+ if a:string[end] == "'"
+ let end = match(a:string, "'", end+1) + 1
+ if end == 0
+ throw "No matching end single quote"
+ endif
+ elseif a:string[end] == '"'
+ " Find a " preceded by an even number of \ (or 0)
+ let pattern = '\%(\\\@<!\%(\\\\\)*\)\@<="'
+ let end = matchend(a:string, pattern, end+1) + 1
+ if end == 0
+ throw "No matching end double quote"
+ endif
+ else " Found ||
+ let end += 2
+ endif
+
+ let end = match(a:string, "[\"'|]", end)
+ endwhile
+
+ if end == 0 || a:string[0 : end - (end > 0)] =~ '^\s*$'
+ throw "Empty element"
+ endif
+
+ if end == -1
+ let rv = [ a:string ]
+ else
+ let rv = [ a:string[0 : end-1] ] + s:SplitCommands(a:string[end+1 : -1])
+ endif
+
+ return rv
+endfunction
+
+" Public Things {{{1
+
+" Command associating a command name with a simple pattern command {{{2
+" AddTabularPattern[!] [<buffer>] name /pattern[/format]
+"
+" If <buffer> is provided, the command will only be available in the current
+" buffer, and will be used instead of any global command with the same name.
+"
+" If a command with the same name and scope already exists, it is an error,
+" unless the ! is provided, in which case the existing command will be
+" replaced.
+"
+" pattern is a regex describing the delimiter to be used.
+"
+" format describes the format pattern to be used. The default will be used if
+" none is provided.
+com! -nargs=+ -bang AddTabularPattern
+ \ call AddTabularPattern(<q-args>, <bang>0)
+
+function! AddTabularPattern(command, force)
+ try
+ let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
+
+ let name = matchstr(rest, '.\{-}\ze\s*/')
+ let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
+
+ let [ pattern, format ] = s:ParsePattern(pattern)
+
+ if empty(name) || empty(pattern)
+ throw "Invalid arguments!"
+ endif
+
+ if !a:force && has_key(commandmap, name)
+ throw string(name) . " is already defined, use ! to overwrite."
+ endif
+
+ let command = "tabular#TabularizeStrings(a:lines, " . string(pattern)
+
+ if !empty(format)
+ let command .= ", " . string(format)
+ endif
+
+ let command .= ")"
+
+ let commandmap[name] = ":call tabular#PipeRange("
+ \ . string(pattern) . ","
+ \ . string(command) . ")"
+ catch
+ echohl ErrorMsg
+ echomsg "AddTabularPattern: " . v:exception
+ echohl None
+ endtry
+endfunction
+
+" Command associating a command name with a pipeline of functions {{{2
+" AddTabularPipeline[!] [<buffer>] name /pattern/ func [ | func2 [ | func3 ] ]
+"
+" If <buffer> is provided, the command will only be available in the current
+" buffer, and will be used instead of any global command with the same name.
+"
+" If a command with the same name and scope already exists, it is an error,
+" unless the ! is provided, in which case the existing command will be
+" replaced.
+"
+" pattern is a regex that will be used to determine which lines will be
+" filtered. If the cursor line doesn't match the pattern, using the command
+" will be a no-op, otherwise the cursor and all contiguous lines matching the
+" pattern will be filtered.
+"
+" Each 'func' argument represents a function to be called. This function
+" will have access to a:lines, a List containing one String per line being
+" filtered.
+com! -nargs=+ -bang AddTabularPipeline
+ \ call AddTabularPipeline(<q-args>, <bang>0)
+
+function! AddTabularPipeline(command, force)
+ try
+ let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
+
+ let name = matchstr(rest, '.\{-}\ze\s*/')
+ let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
+
+ let commands = matchstr(pattern, '^/.\{-}\\\@<!\%(\\\\\)\{-}/\zs.*')
+ let pattern = matchstr(pattern, '/\zs.\{-}\\\@<!\%(\\\\\)\{-}\ze/')
+
+ if empty(name) || empty(pattern)
+ throw "Invalid arguments!"
+ endif
+
+ if !a:force && has_key(commandmap, name)
+ throw string(name) . " is already defined, use ! to overwrite."
+ endif
+
+ let commandlist = s:SplitCommands(commands)
+
+ if empty(commandlist)
+ throw "Must provide a list of functions!"
+ endif
+
+ let cmd = ":call tabular#PipeRange(" . string(pattern)
+
+ for command in commandlist
+ let cmd .= "," . string(command)
+ endfor
+
+ let cmd .= ")"
+
+ let commandmap[name] = cmd
+ catch
+ echohl ErrorMsg
+ echomsg "AddTabularPipeline: " . v:exception
+ echohl None
+ endtry
+endfunction
+
+" Tabularize /pattern[/format] {{{2
+" Tabularize name
+"
+" Align text, either using the given pattern, or the command associated with
+" the given name.
+com! -nargs=+ -range -complete=customlist,<SID>CompleteTabularizeCommand
+ \ Tabularize <line1>,<line2>call Tabularize(<q-args>)
+
+function! Tabularize(command) range
+ let range = a:firstline . ',' . a:lastline
+
+ try
+ let [ pattern, format ] = s:ParsePattern(a:command)
+
+ if !empty(pattern)
+ let cmd = "tabular#TabularizeStrings(a:lines, " . string(pattern)
+
+ if !empty(format)
+ let cmd .= "," . string(format)
+ endif
+
+ let cmd .= ")"
+
+ exe range . 'call tabular#PipeRange(pattern, cmd)'
+ else
+ if exists('b:TabularCommands') && has_key(b:TabularCommands, a:command)
+ let command = b:TabularCommands[a:command]
+ elseif has_key(s:TabularCommands, a:command)
+ let command = s:TabularCommands[a:command]
+ else
+ throw "Unrecognized command " . string(a:command)
+ endif
+
+ exe range . command
+ endif
+ catch
+ echohl ErrorMsg
+ echomsg "Tabularize: " . v:exception
+ echohl None
+ return
+ endtry
+endfunction
+
+" Stupid vimscript crap, part 2 {{{1
+let &cpo = s:savecpo
+unlet s:savecpo
+
+" vim:set sw=2 sts=2 fdm=marker: