Initial commit

This commit is contained in:
Matt Wozniski 2009-03-03 01:20:14 -05:00
commit ef55d4b6eb
3 changed files with 592 additions and 0 deletions

279
autoload/tabular.vim Normal file
View File

@ -0,0 +1,279 @@
" 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)
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
let line[0] = s:StripTrailingSpaces(line[0])
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
" 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 . 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:

280
plugin/Tabular.vim Normal file
View File

@ -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:

33
plugin/TabularMaps.vim Normal file
View File

@ -0,0 +1,33 @@
AddTabularPattern! assignment /[|&+*/%<>=!~-]\@<!\([<>!=]=\|=\~\)\@![|&+*/%<>=!~-]*=/l1r1
AddTabularPattern! two_spaces / /l0
AddTabularPipeline! multiple_spaces / / map(a:lines, "substitute(v:val, ' *', ' ', 'g')") | tabular#TabularizeStrings(a:lines, ' ', 'l0')
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