Tabular.vim 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. " Tabular: Align columnar data using regex-designated column boundaries
  2. " Maintainer: Matthew Wozniski (mjw@drexel.edu)
  3. " Date: Thu, 11 Oct 2007 00:35:34 -0400
  4. " Version: 0.1
  5. " Abort if running in vi-compatible mode or the user doesn't want us.
  6. if &cp || exists('g:tabular_loaded')
  7. if &cp && &verbose
  8. echo "Not loading Tabular in compatible mode."
  9. endif
  10. finish
  11. endif
  12. let g:tabular_loaded = 1
  13. " Stupid vimscript crap {{{1
  14. let s:savecpo = &cpo
  15. set cpo&vim
  16. " Private Things {{{1
  17. " Dictionary of command name to command
  18. let s:TabularCommands = {}
  19. " Generate tab completion list for :Tabularize {{{2
  20. " Return a list of commands that match the command line typed so far.
  21. " NOTE: Tries to handle commands with spaces in the name, but Vim doesn't seem
  22. " to handle that terribly well... maybe I should give up on that.
  23. function! s:CompleteTabularizeCommand(argstart, cmdline, cursorpos)
  24. let names = keys(s:TabularCommands)
  25. if exists("b:TabularCommands")
  26. let names += keys(b:TabularCommands)
  27. endif
  28. let cmdstart = substitute(a:cmdline, '^\s*\S\+\s*', '', '')
  29. return filter(names, 'v:val =~# ''^\V'' . escape(cmdstart, ''\'')')
  30. endfunction
  31. " Choose the proper command map from the given command line {{{2
  32. " Returns [ command map, command line with leading <buffer> removed ]
  33. function! s:ChooseCommandMap(commandline)
  34. let map = s:TabularCommands
  35. let cmd = a:commandline
  36. if cmd =~# '^<buffer>\s\+'
  37. if !exists('b:TabularCommands')
  38. let b:TabularCommands = {}
  39. endif
  40. let map = b:TabularCommands
  41. let cmd = substitute(cmd, '^<buffer>\s\+', '', '')
  42. endif
  43. return [ map, cmd ]
  44. endfunction
  45. " Parse '/pattern/format' into separate pattern and format parts. {{{2
  46. " If parsing fails, return [ '', '' ]
  47. function! s:ParsePattern(string)
  48. if a:string[0] != '/'
  49. return ['','']
  50. endif
  51. let pat = '\\\@<!\%(\\\\\)\{-}\zs/' . tabular#ElementFormatPattern() . '*$'
  52. let format = matchstr(a:string[1:-1], pat)
  53. if !empty(format)
  54. let format = format[1 : -1]
  55. let pattern = a:string[1 : -len(format) - 2]
  56. else
  57. let pattern = a:string[1 : -1]
  58. endif
  59. return [pattern, format]
  60. endfunction
  61. " Split apart a list of | separated expressions. {{{2
  62. function! s:SplitCommands(string)
  63. if a:string =~ '^\s*$'
  64. return []
  65. endif
  66. let end = match(a:string, "[\"'|]")
  67. " Loop until we find a delimiting | or end-of-string
  68. while end != -1 && (a:string[end] != '|' || a:string[end+1] == '|')
  69. if a:string[end] == "'"
  70. let end = match(a:string, "'", end+1) + 1
  71. if end == 0
  72. throw "No matching end single quote"
  73. endif
  74. elseif a:string[end] == '"'
  75. " Find a " preceded by an even number of \ (or 0)
  76. let pattern = '\%(\\\@<!\%(\\\\\)*\)\@<="'
  77. let end = matchend(a:string, pattern, end+1) + 1
  78. if end == 0
  79. throw "No matching end double quote"
  80. endif
  81. else " Found ||
  82. let end += 2
  83. endif
  84. let end = match(a:string, "[\"'|]", end)
  85. endwhile
  86. if end == 0 || a:string[0 : end - (end > 0)] =~ '^\s*$'
  87. throw "Empty element"
  88. endif
  89. if end == -1
  90. let rv = [ a:string ]
  91. else
  92. let rv = [ a:string[0 : end-1] ] + s:SplitCommands(a:string[end+1 : -1])
  93. endif
  94. return rv
  95. endfunction
  96. " Public Things {{{1
  97. " Command associating a command name with a simple pattern command {{{2
  98. " AddTabularPattern[!] [<buffer>] name /pattern[/format]
  99. "
  100. " If <buffer> is provided, the command will only be available in the current
  101. " buffer, and will be used instead of any global command with the same name.
  102. "
  103. " If a command with the same name and scope already exists, it is an error,
  104. " unless the ! is provided, in which case the existing command will be
  105. " replaced.
  106. "
  107. " pattern is a regex describing the delimiter to be used.
  108. "
  109. " format describes the format pattern to be used. The default will be used if
  110. " none is provided.
  111. com! -nargs=+ -bang AddTabularPattern
  112. \ call AddTabularPattern(<q-args>, <bang>0)
  113. function! AddTabularPattern(command, force)
  114. try
  115. let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
  116. let name = matchstr(rest, '.\{-}\ze\s*/')
  117. let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
  118. let [ pattern, format ] = s:ParsePattern(pattern)
  119. if empty(name) || empty(pattern)
  120. throw "Invalid arguments!"
  121. endif
  122. if !a:force && has_key(commandmap, name)
  123. throw string(name) . " is already defined, use ! to overwrite."
  124. endif
  125. let command = "tabular#TabularizeStrings(a:lines, " . string(pattern)
  126. if !empty(format)
  127. let command .= ", " . string(format)
  128. endif
  129. let command .= ")"
  130. let commandmap[name] = ":call tabular#PipeRange("
  131. \ . string(pattern) . ","
  132. \ . string(command) . ")"
  133. catch
  134. echohl ErrorMsg
  135. echomsg "AddTabularPattern: " . v:exception
  136. echohl None
  137. endtry
  138. endfunction
  139. " Command associating a command name with a pipeline of functions {{{2
  140. " AddTabularPipeline[!] [<buffer>] name /pattern/ func [ | func2 [ | func3 ] ]
  141. "
  142. " If <buffer> is provided, the command will only be available in the current
  143. " buffer, and will be used instead of any global command with the same name.
  144. "
  145. " If a command with the same name and scope already exists, it is an error,
  146. " unless the ! is provided, in which case the existing command will be
  147. " replaced.
  148. "
  149. " pattern is a regex that will be used to determine which lines will be
  150. " filtered. If the cursor line doesn't match the pattern, using the command
  151. " will be a no-op, otherwise the cursor and all contiguous lines matching the
  152. " pattern will be filtered.
  153. "
  154. " Each 'func' argument represents a function to be called. This function
  155. " will have access to a:lines, a List containing one String per line being
  156. " filtered.
  157. com! -nargs=+ -bang AddTabularPipeline
  158. \ call AddTabularPipeline(<q-args>, <bang>0)
  159. function! AddTabularPipeline(command, force)
  160. try
  161. let [ commandmap, rest ] = s:ChooseCommandMap(a:command)
  162. let name = matchstr(rest, '.\{-}\ze\s*/')
  163. let pattern = substitute(rest, '.\{-}\s*\ze/', '', '')
  164. let commands = matchstr(pattern, '^/.\{-}\\\@<!\%(\\\\\)\{-}/\zs.*')
  165. let pattern = matchstr(pattern, '/\zs.\{-}\\\@<!\%(\\\\\)\{-}\ze/')
  166. if empty(name) || empty(pattern)
  167. throw "Invalid arguments!"
  168. endif
  169. if !a:force && has_key(commandmap, name)
  170. throw string(name) . " is already defined, use ! to overwrite."
  171. endif
  172. let commandlist = s:SplitCommands(commands)
  173. if empty(commandlist)
  174. throw "Must provide a list of functions!"
  175. endif
  176. let cmd = ":call tabular#PipeRange(" . string(pattern)
  177. for command in commandlist
  178. let cmd .= "," . string(command)
  179. endfor
  180. let cmd .= ")"
  181. let commandmap[name] = cmd
  182. catch
  183. echohl ErrorMsg
  184. echomsg "AddTabularPipeline: " . v:exception
  185. echohl None
  186. endtry
  187. endfunction
  188. " Tabularize /pattern[/format] {{{2
  189. " Tabularize name
  190. "
  191. " Align text, either using the given pattern, or the command associated with
  192. " the given name.
  193. com! -nargs=+ -range -complete=customlist,<SID>CompleteTabularizeCommand
  194. \ Tabularize <line1>,<line2>call Tabularize(<q-args>)
  195. function! Tabularize(command) range
  196. let range = a:firstline . ',' . a:lastline
  197. try
  198. let [ pattern, format ] = s:ParsePattern(a:command)
  199. if !empty(pattern)
  200. let cmd = "tabular#TabularizeStrings(a:lines, " . string(pattern)
  201. if !empty(format)
  202. let cmd .= "," . string(format)
  203. endif
  204. let cmd .= ")"
  205. exe range . 'call tabular#PipeRange(pattern, cmd)'
  206. else
  207. if exists('b:TabularCommands') && has_key(b:TabularCommands, a:command)
  208. let command = b:TabularCommands[a:command]
  209. elseif has_key(s:TabularCommands, a:command)
  210. let command = s:TabularCommands[a:command]
  211. else
  212. throw "Unrecognized command " . string(a:command)
  213. endif
  214. exe range . command
  215. endif
  216. catch
  217. echohl ErrorMsg
  218. echomsg "Tabularize: " . v:exception
  219. echohl None
  220. return
  221. endtry
  222. endfunction
  223. " Stupid vimscript crap, part 2 {{{1
  224. let &cpo = s:savecpo
  225. unlet s:savecpo
  226. " vim:set sw=2 sts=2 fdm=marker: