Permalink
Browse files

Tabular, and | auto-aligns cucumber tables

  • Loading branch information...
1 parent be5e227 commit 1548f6fe5c6cc5d4f7ff4c6f13b13dbb6d1303ed @jyurek committed Oct 18, 2011
@@ -0,0 +1 @@
+/doc/tags
@@ -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
@@ -0,0 +1,327 @@
+" 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 len = len(a:string)
+ let searchoff = 0
+
+ while 1
+ let mid = match(a:string, a:delim, beg + searchoff, 1)
+ if mid == -1 || mid == len
+ break
+ endif
+
+ let matchstr = matchstr(a:string, a:delim, beg + searchoff, 1)
+ let length = strlen(matchstr)
+
+ if length == 0 && beg == mid
+ " Zero-length match for a zero-length delimiter - advance past it
+ let searchoff += 1
+ continue
+ endif
+
+ if beg == mid
+ let rv += [ "" ]
+ else
+ let rv += [ a:string[beg : mid-1] ]
+ endif
+
+ let rv += [ matchstr ]
+
+ let beg = mid + length
+ let searchoff = 0
+ endwhile
+
+ let rv += [ strpart(a:string, beg) ]
+
+ 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
+
+function! s:SplitDelimTest(string, delim, expected)
+ let result = s:SplitDelim(a:string, a:delim)
+
+ if result !=# a:expected
+ echomsg 'Test failed!'
+ echomsg ' string=' . string(a:string) . ' delim=' . string(a:delim)
+ echomsg ' Returned=' . string(result)
+ echomsg ' Expected=' . string(a:expected)
+ endif
+endfunction
+
+function! tabular#SplitDelimUnitTest()
+ let assignment = '[|&+*/%<>=!~-]\@<!\([<>!=]=\|=\~\)\@![|&+*/%<>=!~-]*='
+ let two_spaces = ' '
+ let ternary_operator = '^.\{-}\zs?\|:'
+ let cpp_io = '<<\|>>'
+ let pascal_assign = ':='
+ let trailing_c_comments = '\/\*\|\*\/\|\/\/'
+
+ call s:SplitDelimTest('a+=b', assignment, ['a', '+=', 'b'])
+ call s:SplitDelimTest('a-=b', assignment, ['a', '-=', 'b'])
+ call s:SplitDelimTest('a!=b', assignment, ['a!=b'])
+ call s:SplitDelimTest('a==b', assignment, ['a==b'])
+ call s:SplitDelimTest('a&=b', assignment, ['a', '&=', 'b'])
+ call s:SplitDelimTest('a|=b', assignment, ['a', '|=', 'b'])
+ call s:SplitDelimTest('a=b=c', assignment, ['a', '=', 'b', '=', 'c'])
+
+ call s:SplitDelimTest('a b c', two_spaces, ['a', ' ', 'b', ' ', 'c'])
+ call s:SplitDelimTest('a b c', two_spaces, ['a b', ' ', ' c'])
+ call s:SplitDelimTest('ab c', two_spaces, ['ab', ' ', '', ' ', 'c'])
+
+ call s:SplitDelimTest('a?b:c', ternary_operator, ['a', '?', 'b', ':', 'c'])
+
+ call s:SplitDelimTest('a<<b<<c', cpp_io, ['a', '<<', 'b', '<<', 'c'])
+
+ call s:SplitDelimTest('a:=b=c', pascal_assign, ['a', ':=', 'b=c'])
+
+ call s:SplitDelimTest('x//foo', trailing_c_comments, ['x', '//', 'foo'])
+ call s:SplitDelimTest('x/*foo*/',trailing_c_comments, ['x', '/*', 'foo', '*/', ''])
+
+ call s:SplitDelimTest('#ab#cd#ef', '[^#]*', ['#', 'ab', '#', 'cd', '#', 'ef', ''])
+ call s:SplitDelimTest('#ab#cd#ef', '#\zs', ['#', '', 'ab#', '', 'cd#', '', 'ef'])
+endfunction
+
+" Stupid vimscript crap, part 2 {{{1
+let &cpo = s:savecpo
+unlet s:savecpo
+
+" vim:set sw=2 sts=2 fdm=marker:
Oops, something went wrong. Retry.

0 comments on commit 1548f6f

Please sign in to comment.