" Tabular: Align columnar data using regex-designated column boundaries " Maintainer: Matthew Wozniski (godlygeek@gmail.com) " Date: Thu, 03 May 2012 20:49:32 -0400 " Version: 1.0 " " Long Description: " 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. " " License: " Copyright (c) 2012, Matthew J. Wozniski " All rights reserved. " " Redistribution and use in source and binary forms, with or without " modification, are permitted provided that the following conditions are met: " * Redistributions of source code must retain the above copyright notice, " this list of conditions and the following disclaimer. " * Redistributions in binary form must reproduce the above copyright " notice, this list of conditions and the following disclaimer in the " documentation and/or other materials provided with the distribution. " * The names of the contributors may not be used to endorse or promote " products derived from this software without specific prior written " permission. " " THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS " OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES " OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN " NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, " INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT " LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, " OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF " LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING " NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, " EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. " 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 removed ] function! s:ChooseCommandMap(commandline) let map = s:TabularCommands let cmd = a:commandline if cmd =~# '^\s\+' if !exists('b:TabularCommands') let b:TabularCommands = {} endif let map = b:TabularCommands let cmd = substitute(cmd, '^\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 = '\\\@ 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[!] [] name /pattern[/format] " " If 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(, 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] = { 'pattern' : pattern, 'commands' : [ command ] } catch echohl ErrorMsg echomsg "AddTabularPattern: " . v:exception echohl None endtry endfunction " Command associating a command name with a pipeline of functions {{{2 " AddTabularPipeline[!] [] name /pattern/ func [ | func2 [ | func3 ] ] " " If 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(, 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, '^/.\{-}\\\@CompleteTabularizeCommand \ Tabularize ,call Tabularize() function! Tabularize(command, ...) range let piperange_opt = {} if a:0 let piperange_opt = a:1 endif if empty(a:command) if !exists("s:last_tabularize_command") echohl ErrorMsg echomsg "Tabularize hasn't been called yet; no pattern/command to reuse!" echohl None return endif else let s:last_tabularize_command = a:command endif let command = s:last_tabularize_command let range = a:firstline . ',' . a:lastline try let [ pattern, format ] = s:ParsePattern(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#PipeRangeWithOptions(pattern, [ cmd ], ' \ . 'piperange_opt)' else if exists('b:TabularCommands') && has_key(b:TabularCommands, command) let usercmd = b:TabularCommands[command] elseif has_key(s:TabularCommands, command) let usercmd = s:TabularCommands[command] else throw "Unrecognized command " . string(command) endif exe range . 'call tabular#PipeRangeWithOptions(usercmd["pattern"], ' \ . 'usercmd["commands"], piperange_opt)' endif catch echohl ErrorMsg echomsg "Tabularize: " . v:exception echohl None return endtry endfunction " GTabularize /pattern[/format] {{{2 " GTabularize name " " Align text on only matching lines, either using the given pattern, or the " command associated with the given name. Mnemonically, this is similar to " the :global command, which takes some action on all rows matching a pattern " in a range. This command is different from normal :Tabularize in 3 ways: " 1) If a line in the range does not match the pattern, it will be left " unchanged, and not in any way affect the outcome of other lines in the " range (at least, normally - but Pipelines can and will still look at " non-matching rows unless they are specifically written to be aware of " tabular#DoGTabularize() and handle it appropriately). " 2) No automatic range determination - :Tabularize automatically expands " a single-line range (or a call with no range) to include all adjacent " matching lines. That behavior does not make sense for this command. " 3) If called without a range, it will act on all lines in the buffer (like " :global) rather than only a single line com! -nargs=* -range=% -complete=customlist,CompleteTabularizeCommand \ GTabularize , \ call Tabularize(, { 'mode': 'GTabularize' } ) " Stupid vimscript crap, part 2 {{{1 let &cpo = s:savecpo unlet s:savecpo " vim:set sw=2 sts=2 fdm=marker: