csv.vim 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116
  1. " Filetype plugin for editing CSV files. "{{{1
  2. " Author: Christian Brabandt <cb@256bit.org>
  3. " Version: 0.26
  4. " Script: http://www.vim.org/scripts/script.php?script_id=2830
  5. " License: VIM License
  6. " Last Change: Wed, 25 Jul 2012 22:22:28 +0200
  7. " Documentation: see :help ft-csv.txt
  8. " GetLatestVimScripts: 2830 25 :AutoInstall: csv.vim
  9. "
  10. " Some ideas are taken from the wiki http://vim.wikia.com/wiki/VimTip667
  11. " though, implementation differs.
  12. " Plugin folklore "{{{2
  13. if v:version < 700 || exists('b:did_ftplugin')
  14. finish
  15. endif
  16. let b:did_ftplugin = 1
  17. let s:cpo_save = &cpo
  18. set cpo&vim
  19. " Function definitions: "{{{2
  20. fu! <sid>Warn(mess) "{{{3
  21. echohl WarningMsg
  22. echomsg "CSV: " . a:mess
  23. echohl Normal
  24. endfu
  25. fu! <sid>Init() "{{{3
  26. " Hilight Group for Columns
  27. if exists("g:csv_hiGroup")
  28. let s:hiGroup = g:csv_hiGroup
  29. else
  30. let s:hiGroup="WildMenu"
  31. endif
  32. if !exists("g:csv_hiHeader")
  33. let s:hiHeader = "Title"
  34. else
  35. let s:hiHeader = g:csv_hiHeader
  36. endif
  37. exe "hi link CSVHeaderLine" s:hiHeader
  38. " Determine default Delimiter
  39. if !exists("g:csv_delim")
  40. let b:delimiter=<SID>GetDelimiter()
  41. else
  42. let b:delimiter=g:csv_delim
  43. endif
  44. " Define custom commentstring
  45. if !exists("g:csv_comment")
  46. let b:csv_cmt = split(&cms, '%s')
  47. else
  48. let b:csv_cmt = split(g:csv_comment, '%s')
  49. endif
  50. if empty(b:delimiter) && !exists("b:csv_fixed_width")
  51. call <SID>Warn("No delimiter found. See :h csv-delimiter to set it manually!")
  52. " Use a sane default as delimiter:
  53. let b:delimiter = ','
  54. endif
  55. let s:del='\%(' . b:delimiter . '\|$\)'
  56. " Pattern for matching a single column
  57. if !exists("g:csv_strict_columns") && !exists("g:csv_col")
  58. \ && !exists("b:csv_fixed_width")
  59. " - Allow double quotes as escaped quotes only insides double quotes
  60. " - Allow linebreaks only, if g:csv_nl isn't set (this is
  61. " only allowed in double quoted strings see RFC4180), though this
  62. " does not work with :WhatColumn and might mess up syntax
  63. " highlighting.
  64. " - optionally allow whitespace in front of the fields (to make it
  65. " work with :ArrangeCol (that is actually not RFC4180 valid))
  66. " - Should work with most ugly solutions that are available
  67. let b:col='\%(\%(\%(' . (b:delimiter !~ '\s' ? '\s*' : '') .
  68. \ '"\%(' . (exists("g:csv_nl") ? '\_' : '' ) .
  69. \ '[^"]\|""\)*"\)' . s:del . '\)\|\%(' .
  70. \ '[^' . b:delimiter . ']*' . s:del . '\)\)'
  71. elseif !exists("g:csv_col") && exists("g:csv_strict_columns")
  72. " strict columns
  73. let b:col='\%([^' . b:delimiter . ']*' . s:del . '\)'
  74. elseif exists("b:csv_fixed_width")
  75. " Fixed width column
  76. let b:col=''
  77. " Check for sane default
  78. if b:csv_fixed_width =~? '[^0-9,]'
  79. call <sid>Warn("Please specify the list of character columns" .
  80. \ "like this: '1,3,5'. See also :h csv-fixedwidth")
  81. return
  82. endif
  83. let b:csv_fixed_width_cols=split(b:csv_fixed_width, ',')
  84. " Force evaluating as numbers
  85. call map(b:csv_fixed_width_cols, 'v:val+0')
  86. else
  87. " User given column definition
  88. let b:col = g:csv_col
  89. endif
  90. " set filetype specific options
  91. call <sid>LocalSettings('all')
  92. " define buffer-local commands
  93. call <SID>CommandDefinitions()
  94. " Check Header line
  95. " Defines which line is considered to be a header line
  96. call <sid>CheckHeaderLine()
  97. " CSV specific mappings
  98. call <SID>CSVMappings()
  99. " force reloading CSV Syntax Highlighting
  100. if exists("b:current_syntax")
  101. unlet b:current_syntax
  102. " Force reloading syntax file
  103. endif
  104. call <sid>DoAutoCommands()
  105. " enable CSV Menu
  106. call <sid>Menu(1)
  107. call <sid>DisableFolding()
  108. silent do Syntax
  109. " Remove configuration variables
  110. let b:undo_ftplugin .= "| unlet! b:delimiter b:col"
  111. \ . "| unlet! b:csv_fixed_width_cols b:csv_filter"
  112. \ . "| unlet! b:csv_fixed_width b:csv_list b:col_width"
  113. \ . "| unlet! b:csv_SplitWindow b:csv_headerline"
  114. \ . "| unlet! b:csv_thousands_sep b:csv_decimal_sep"
  115. \. " | unlet! b:browsefilter"
  116. " Delete all functions
  117. " disabled currently, because otherwise when switching ft
  118. " I think, all functions need to be read in again and this
  119. " costs time.
  120. "
  121. " let b:undo_ftplugin .= "| delf <sid>Warn | delf <sid>Init |
  122. " \ delf <sid>GetPat | delf <sid>SearchColumn | delf <sid>DelColumn |
  123. " \ delf <sid>HiCol | delf <sid>GetDelimiter | delf <sid>WColumn |
  124. " \ delf <sid>MaxColumns | delf <sid>ColWidth | delf <sid>ArCol |
  125. " \ delf <sid>PrepUnArCol | delf <sid>UnArCol |
  126. " \ delf <sid>CalculateColumnWidth | delf <sid>Columnize |
  127. " \ delf <sid>GetColPat | delf <sid>SplitHeaderLine |
  128. " \ delf <sid>SplitHeaderToggle | delf <sid>MoveCol |
  129. " \ delf <sid>SortComplete | delf <sid>SortList | delf <sid>Sort |
  130. " \ delf CSV_WCol | delf <sid>CopyCol | delf <sid>MoveColumn |
  131. " \ delf <sid>SumColumn csv#EvalColumn | delf <sid>DoForEachColumn |
  132. " \ delf <sid>PrepareDoForEachColumn | delf <sid>CSVMappings |
  133. " \ delf <sid>Map | delf <sid>EscapeValue | delf <sid>FoldValue |
  134. " \ delf <sid>PrepareFolding | delf <sid>OutputFilters |
  135. " \ delf <sid>SortFilter | delf <sid>GetColumn |
  136. " \ delf <sid>RemoveLastItem | delf <sid>DisableFolding |
  137. " \ delf <sid>GetSID | delf <sid>CheckHeaderLine |
  138. " \ delf <sid>AnalyzeColumn | delf <sid>Vertfold |
  139. " \ delf <sid>InitCSVFixedWidth | delf <sid>LocalCmd |
  140. " \ delf <sid>CommandDefinitions | delf <sid>NumberFormat |
  141. " \ delf <sid>NewRecord | delf <sid>MoveOver | delf <sid>Menu |
  142. " \ delf <sid>NewDelimiter | delf <sid>DuplicateRows | delf <sid>IN |
  143. " \ delf <sid>SaveOptions | delf <sid>CheckDuplicates |
  144. " \ delf <sid>CompleteColumnNr | delf <sid>CSVPat | delf <sid>Transpose |
  145. " \ delf <sid>LocalSettings()
  146. endfu
  147. fu! <sid>LocalSettings(type) "{{{3
  148. if a:type == 'all'
  149. " CSV local settings
  150. setl nostartofline tw=0 nowrap
  151. " undo when setting a new filetype
  152. let b:undo_ftplugin = "setlocal sol& tw< wrap<"
  153. " Set browsefilter
  154. if (v:version > 703 || (v:version == 703 && has("patch593")))
  155. \ && exists("browsefilter")
  156. let b:browsefilter="CSV Files (*.csv, *.dat)\t*.csv;*.dat\n".
  157. \ "All Files\t*.*\n"
  158. endif
  159. if has("conceal")
  160. setl cole=2 cocu=nc
  161. let b:undo_ftplugin .= '| setl cole< cocu< '
  162. endif
  163. endif
  164. if a:type == 'all' || a:type == 'fold'
  165. " Be sure to also fold away single screen lines
  166. setl fen fdm=expr fdl=0 fdc=2 fml=0
  167. let &foldtext=strlen(v:folddashes) . ' lines hidden'
  168. setl fillchars-=fold:-
  169. " undo settings:
  170. let b:undo_ftplugin .=
  171. \ "| setl fen< fdm< fdl< fdc< fml< fdt&vim fcs& fde<"
  172. endif
  173. endfu
  174. fu! <sid>DoAutoCommands() "{{{3
  175. " Highlight column, on which the cursor is?
  176. if exists("g:csv_highlight_column") && g:csv_highlight_column =~? 'y' &&
  177. \ !exists("#CSV_HI#CursorMoved")
  178. aug CSV_HI
  179. au!
  180. au CursorMoved <buffer> HiColumn
  181. aug end
  182. " Set highlighting for column, on which the cursor is currently
  183. HiColumn
  184. elseif exists("#CSV_HI#CursorMoved")
  185. aug CSV_HI
  186. au! CursorMoved <buffer>
  187. aug end
  188. aug! CSV_HI
  189. " Remove any existing highlighting
  190. HiColumn!
  191. endif
  192. " undo autocommand:
  193. let b:undo_ftplugin .= '| exe "sil! au! CSV_HI CursorMoved <buffer> "'
  194. let b:undo_ftplugin .= '| exe "sil! aug! CSV_HI" |exe "sil! HiColumn!"'
  195. " Visually arrange columns when opening a csv file
  196. if exists("g:csv_autocmd_arrange") &&
  197. \ !exists("#CSV_Edit#BufReadPost")
  198. aug CSV_Edit
  199. au!
  200. au BufReadPost,BufWritePost *.csv,*.dat :sil %ArrangeColumn
  201. au BufWritePre *.csv,*.dat :sil %UnArrangeColumn
  202. aug end
  203. elseif exists("#CSV_Edit#BufReadPost")
  204. aug CSV_Edit
  205. au!
  206. aug end
  207. aug! CSV_Edit
  208. endif
  209. " undo autocommand:
  210. let b:undo_ftplugin .= '| exe "sil! au! CSV_Edit BufRead,BufWritePost,BufWritePre *.csv,*.dat "'
  211. let b:undo_ftplugin .= '| exe "sil! aug! CSV_Edit"'
  212. if !exists("#CSV#ColorScheme")
  213. " Make sure, syntax highlighting is applied
  214. " after changing the colorscheme
  215. augroup CSV
  216. au!
  217. au ColorScheme *.csv,*.dat,*.tsv,*.tab do Syntax
  218. augroup end
  219. endif
  220. let b:undo_ftplugin .= '| exe "sil! au! CSV ColorScheme *.csv,*.dat "'
  221. let b:undo_ftplugin .= '| exe "sil! aug! CSV"'
  222. if has("gui_running") && !exists("#CSV_Menu#FileType")
  223. augroup CSV_Menu
  224. au!
  225. au FileType csv call <sid>Menu(1)
  226. au BufEnter <buffer> call <sid>Menu(1) " enable
  227. au BufLeave <buffer> call <sid>Menu(0) " disable
  228. au BufNewFile,BufNew * call <sid>Menu(0)
  229. augroup END
  230. "let b:undo_ftplugin .= '| sil! amenu disable CSV'
  231. let b:undo_ftplugin .= '| sil! call <sid>Menut(0)'
  232. endif
  233. endfu
  234. fu! <sid>GetPat(colnr, maxcolnr, pat) "{{{3
  235. if a:colnr > 1 && a:colnr < a:maxcolnr
  236. if !exists("b:csv_fixed_width_cols")
  237. return '^' . <SID>GetColPat(a:colnr-1,0) . '\%([^' .
  238. \ b:delimiter . ']\{-}\)\?\zs' . a:pat . '\ze' .
  239. \ '\%([^' . b:delimiter .']\{-}\)\?' .
  240. \ b:delimiter . <SID>GetColPat(a:maxcolnr - a:colnr, 0) .
  241. \ '$'
  242. else
  243. return '\%' . b:csv_fixed_width_cols[(a:colnr - 1)] . 'c\zs'
  244. \ . a:pat . '.\{-}\ze\%'
  245. \ . (b:csv_fixed_width_cols[a:colnr]) . 'c\ze'
  246. endif
  247. elseif a:colnr == a:maxcolnr
  248. if !exists("b:csv_fixed_width_cols")
  249. return '^' . <SID>GetColPat(a:colnr - 1,0) .
  250. \ '\%([^' . b:delimiter .
  251. \ ']\{-}\)\?\zs' . a:pat . '\ze'
  252. else
  253. return '\%' . b:csv_fixed_width_cols[-1] .
  254. \ 'c\zs' . a:pat . '\ze'
  255. endif
  256. else " colnr = 1
  257. if !exists("b:csv_fixed_width_cols")
  258. return '^' . '\%([^' . b:delimiter . ']\{-}\)\?\zs' . a:pat .
  259. \ '\ze\%([^' . b:delimiter . ']*\)\?' . b:delimiter .
  260. \ <SID>GetColPat(a:maxcolnr -1 , 0) . '$'
  261. else
  262. return a:pat . '\ze.\{-}\%' . b:csv_fixed_width_cols[1] . 'c'
  263. endif
  264. endif
  265. return ''
  266. endfu
  267. fu! <sid>SearchColumn(arg) "{{{3
  268. try
  269. let arglist=split(a:arg)
  270. if len(arglist) == 1
  271. let colnr=<SID>WColumn()
  272. let pat=substitute(arglist[0], '^\(.\)\(.*\)\1$', '\2', '')
  273. if pat == arglist[0]
  274. throw "E684"
  275. endif
  276. else
  277. " Determine whether the first word in the argument is a number
  278. " (of the column to search).
  279. let colnr = substitute( a:arg, '^\s*\(\d\+\)\s.*', '\1', '' )
  280. " If it is _not_ a number,
  281. if colnr == a:arg
  282. " treat the whole argument as the pattern.
  283. let pat = substitute(a:arg,
  284. \ '^\s*\(\S\)\(.*\)\1\s*$', '\2', '' )
  285. if pat == a:arg
  286. throw "E684"
  287. endif
  288. let colnr = <SID>WColumn()
  289. else
  290. " if the first word tells us the number of the column,
  291. " treat the rest of the argument as the pattern.
  292. let pat = substitute(a:arg,
  293. \ '^\s*\d\+\s*\(\S\)\(.*\)\1\s*$', '\2', '' )
  294. if pat == a:arg
  295. throw "E684"
  296. endif
  297. endif
  298. " let colnr=arglist[0]
  299. " let pat=substitute(arglist[1], '^\(.\)\(.*\)\1$', '\2', '')
  300. " if pat == arglist[1]
  301. " throw "E684"
  302. " endif
  303. endif
  304. "catch /^Vim\%((\a\+)\)\=:E684/
  305. catch /E684/ " catch error index out of bounds
  306. call <SID>Warn("Error! Usage :SearchInColumn [<colnr>] /pattern/")
  307. return 1
  308. endtry
  309. let maxcolnr = <SID>MaxColumns()
  310. if colnr > maxcolnr
  311. call <SID>Warn("There exists no column " . colnr)
  312. return 1
  313. endif
  314. let @/ = <sid>GetPat(colnr, maxcolnr, pat)
  315. try
  316. norm! n
  317. catch /^Vim\%((\a\+)\)\=:E486/
  318. " Pattern not found
  319. echohl Error
  320. echomsg "E486: Pattern not found in column " . colnr . ": " . pat
  321. if &vbs > 0
  322. echomsg substitute(v:exception, '^[^:]*:', '','')
  323. endif
  324. echohl Normal
  325. endtry
  326. endfu
  327. fu! <sid>DeleteColumn(arg) "{{{3
  328. let _wsv = winsaveview()
  329. if a:arg =~ '^[/]'
  330. let i = 0
  331. let pat = a:arg[1:]
  332. call cursor(1,1)
  333. while search(pat, 'cW')
  334. " Delete matching column
  335. sil call <sid>DelColumn('')
  336. let i+=1
  337. endw
  338. else
  339. let i = 1
  340. sil call <sid>DelColumn(a:arg)
  341. endif
  342. if i > 1
  343. call <sid>Warn(printf("%d columns deleted", i))
  344. else
  345. call <sid>Warn("1 column deleted")
  346. endif
  347. call winrestview(_wsv)
  348. endfu
  349. fu! <sid>DelColumn(colnr) "{{{3
  350. let maxcolnr = <SID>MaxColumns()
  351. let _p = getpos('.')
  352. if empty(a:colnr)
  353. let colnr=<SID>WColumn()
  354. else
  355. let colnr=a:colnr
  356. endif
  357. if colnr > maxcolnr
  358. call <SID>Warn("There exists no column " . colnr)
  359. return
  360. endif
  361. if colnr != '1'
  362. if !exists("b:csv_fixed_width_cols")
  363. let pat= '^' . <SID>GetColPat(colnr-1,1) . b:col
  364. else
  365. let pat= <SID>GetColPat(colnr,0)
  366. endif
  367. else
  368. " distinction between csv and fixed width does not matter here
  369. let pat= '^' . <SID>GetColPat(colnr,0)
  370. endif
  371. if &ro
  372. let ro = 1
  373. setl noro
  374. else
  375. let ro = 0
  376. endif
  377. exe ':%s/' . escape(pat, '/') . '//'
  378. call setpos('.', _p)
  379. if ro
  380. setl ro
  381. endif
  382. endfu
  383. fu! <sid>HiCol(colnr, bang) "{{{3
  384. if a:colnr > <SID>MaxColumns() && !a:bang
  385. call <SID>Warn("There exists no column " . a:colnr)
  386. return
  387. endif
  388. if !a:bang
  389. if empty(a:colnr)
  390. let colnr=<SID>WColumn()
  391. else
  392. let colnr=a:colnr
  393. endif
  394. if colnr==1
  395. let pat='^'. <SID>GetColPat(colnr,0)
  396. elseif !exists("b:csv_fixed_width_cols")
  397. let pat='^'. <SID>GetColPat(colnr-1,1) . b:col
  398. else
  399. let pat=<SID>GetColPat(colnr,0)
  400. endif
  401. endif
  402. if exists("*matchadd")
  403. if exists("s:matchid")
  404. " ignore errors, that come from already deleted matches
  405. sil! call matchdelete(s:matchid)
  406. endif
  407. " Additionally, filter all matches, that could have been used earlier
  408. let matchlist=getmatches()
  409. call filter(matchlist, 'v:val["group"] !~ s:hiGroup')
  410. call setmatches(matchlist)
  411. if a:bang
  412. return
  413. endif
  414. let s:matchid=matchadd(s:hiGroup, pat, 0)
  415. elseif !a:bang
  416. exe ":2match " . s:hiGroup . ' /' . pat . '/'
  417. endif
  418. endfu
  419. fu! <sid>GetDelimiter() "{{{3
  420. if !exists("b:csv_fixed_width_cols")
  421. let _cur = getpos('.')
  422. let _s = @/
  423. let Delim= {0: ';', 1: ',', 2: '|', 3: ' '}
  424. let temp = {}
  425. " :silent :s does not work with lazyredraw
  426. let _lz = &lz
  427. set nolz
  428. for i in values(Delim)
  429. redir => temp[i]
  430. exe "silent! %s/" . i . "/&/nge"
  431. redir END
  432. endfor
  433. let &lz = _lz
  434. let Delim = map(temp, 'matchstr(substitute(v:val, "\n", "", ""), "^\\d\\+")')
  435. let Delim = filter(temp, 'v:val=~''\d''')
  436. let max = max(values(temp))
  437. let result=[]
  438. call setpos('.', _cur)
  439. let @/ = _s
  440. for [key, value] in items(Delim)
  441. if value == max
  442. return key
  443. endif
  444. endfor
  445. return ''
  446. else
  447. " There is no delimiter for fixedwidth files
  448. return ''
  449. endif
  450. endfu
  451. fu! <sid>WColumn(...) "{{{3
  452. " Return on which column the cursor is
  453. let _cur = getpos('.')
  454. if !exists("b:csv_fixed_width_cols")
  455. let line=getline('.')
  456. " move cursor to end of field
  457. "call search(b:col, 'ec', line('.'))
  458. call search(b:col, 'ec')
  459. let end=col('.')-1
  460. let fields=(split(line[0:end],b:col.'\zs'))
  461. let ret=len(fields)
  462. if exists("a:1") && a:1 > 0
  463. " bang attribute
  464. let head = split(getline(1),b:col.'\zs')
  465. " remove preceeding whitespace
  466. let ret = substitute(head[ret-1], '^\s\+', '', '')
  467. " remove delimiter
  468. let ret = substitute(ret, b:delimiter. '$', '', '')
  469. endif
  470. else
  471. let temp=getpos('.')[2]
  472. let j=1
  473. let ret = 1
  474. for i in sort(b:csv_fixed_width_cols, "<sid>SortList")
  475. if temp >= i
  476. let ret = j
  477. endif
  478. let j += 1
  479. endfor
  480. endif
  481. call setpos('.',_cur)
  482. return ret
  483. endfu
  484. fu! <sid>MaxColumns(...) "{{{3
  485. if exists("a:0") && a:0 == 1
  486. let this_col = 1
  487. else
  488. let this_col = 0
  489. endif
  490. "return maximum number of columns in first 10 lines
  491. if !exists("b:csv_fixed_width_cols")
  492. if this_col
  493. let i = a:1
  494. else
  495. let i = 1
  496. endif
  497. while 1
  498. let l = getline(i, i+10)
  499. " Filter comments out
  500. let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
  501. call filter(l, 'v:val !~ pat')
  502. if !empty(l) || this_col
  503. break
  504. else
  505. let i+=10
  506. endif
  507. endw
  508. if empty(l)
  509. throw 'csv:no_col'
  510. endif
  511. let fields=[]
  512. let result=0
  513. for item in l
  514. let temp=len(split(item, b:col.'\zs'))
  515. let result=(temp>result ? temp : result)
  516. endfor
  517. return result
  518. else
  519. return len(b:csv_fixed_width_cols)
  520. endif
  521. endfu
  522. fu! <sid>ColWidth(colnr) "{{{3
  523. " Return the width of a column
  524. " Internal function
  525. let width=20 "Fallback (wild guess)
  526. let tlist=[]
  527. if !exists("b:csv_fixed_width_cols")
  528. if !exists("b:csv_list")
  529. let b:csv_list=getline(1,'$')
  530. let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
  531. call filter(b:csv_list, 'v:val !~ pat')
  532. call map(b:csv_list, 'split(v:val, b:col.''\zs'')')
  533. endif
  534. try
  535. for item in b:csv_list
  536. call add(tlist, item[a:colnr-1])
  537. endfor
  538. " we have a list of the first 10 rows
  539. " Now transform it to a list of field a:colnr
  540. " and then return the maximum strlen
  541. " That could be done in 1 line, but that would look ugly
  542. "call map(list, 'split(v:val, b:col."\\zs")[a:colnr-1]')
  543. call map(tlist, 'substitute(v:val, ".", "x", "g")')
  544. call map(tlist, 'strlen(v:val)')
  545. return max(tlist)
  546. catch
  547. throw "ColWidth-error"
  548. return width
  549. endtry
  550. else
  551. let cols = len(b:csv_fixed_width_cols)
  552. if a:colnr == cols
  553. return strlen(substitute(getline('$'), '.', 'x', 'g')) -
  554. \ b:csv_fixed_width_cols[cols-1] + 1
  555. elseif a:colnr < cols && a:colnr > 0
  556. return b:csv_fixed_width_cols[a:colnr] -
  557. \ b:csv_fixed_width_cols[(a:colnr - 1)]
  558. else
  559. throw "ColWidth-error"
  560. return 0
  561. endif
  562. endif
  563. endfu
  564. fu! <sid>ArrangeCol(first, last, bang) range "{{{3
  565. "TODO: Why doesn't that work?
  566. " is this because of the range flag?
  567. " It's because of the way, Vim works with
  568. " a:firstline and a:lastline parameter, therefore
  569. " explicitly give the range as argument to the function
  570. if exists("b:csv_fixed_width_cols")
  571. " Nothing to do
  572. call <sid>Warn("ArrangeColumn does not work with fixed width column!")
  573. return
  574. endif
  575. let cur=winsaveview()
  576. if a:bang || !exists("b:col_width")
  577. " Force recalculation of Column width
  578. call <sid>CalculateColumnWidth()
  579. endif
  580. if &ro
  581. " Just in case, to prevent the Warning
  582. " Warning: W10: Changing read-only file
  583. let ro = 1
  584. setl noro
  585. else
  586. let ro = 0
  587. endif
  588. exe a:first . ',' . a:last .'s/' . (b:col) .
  589. \ '/\=<SID>Columnize(submatch(0))/' . (&gd ? '' : 'g')
  590. " Clean up variables, that were only needed for <sid>Columnize() function
  591. unlet! s:columnize_count s:max_cols s:prev_line
  592. if ro
  593. setl ro
  594. unlet ro
  595. endif
  596. call winrestview(cur)
  597. endfu
  598. fu! <sid>PrepUnArrangeCol(first, last) "{{{3
  599. " Because of the way, Vim works with
  600. " a:firstline and a:lastline parameter,
  601. " explicitly give the range as argument to the function
  602. if exists("b:csv_fixed_width_cols")
  603. " Nothing to do
  604. call <sid>Warn("UnArrangeColumn does not work with fixed width column!")
  605. return
  606. endif
  607. let cur=winsaveview()
  608. if &ro
  609. " Just in case, to prevent the Warning
  610. " Warning: W10: Changing read-only file
  611. setl noro
  612. endif
  613. exe a:first . ',' . a:last .'s/' . (b:col) .
  614. \ '/\=<SID>UnArrangeCol(submatch(0))/' . (&gd ? '' : 'g')
  615. " Clean up variables, that were only needed for <sid>Columnize() function
  616. call winrestview(cur)
  617. endfu
  618. fu! <sid>UnArrangeCol(match) "{{{3
  619. " Strip leading white space, also trims empty records:
  620. return substitute(a:match, '^\s\+', '', '')
  621. " only strip leading white space, if a non-white space follows:
  622. "return substitute(a:match, '^\s\+\ze\S', '', '')
  623. endfu
  624. fu! <sid>CalculateColumnWidth() "{{{3
  625. " Internal function, not called from external,
  626. " does not work with fixed width columns
  627. let b:col_width=[]
  628. " Force recalculating the Column width
  629. unlet! b:csv_list
  630. let s:max_cols=<SID>MaxColumns()
  631. try
  632. for i in range(1,s:max_cols)
  633. call add(b:col_width, <SID>ColWidth(i))
  634. endfor
  635. catch /ColWidth/
  636. call <sid>Warn("Error: getting Column Width, using default!")
  637. endtry
  638. " delete buffer content in variable b:csv_list,
  639. " this was only necessary for calculating the max width
  640. unlet! b:csv_list
  641. endfu
  642. fu! <sid>Columnize(field) "{{{3
  643. " Internal function, not called from external,
  644. " does not work with fixed width columns
  645. if !exists("s:columnize_count")
  646. let s:columnize_count = 0
  647. endif
  648. if !exists("s:max_cols")
  649. let s:max_cols = len(b:col_width)
  650. endif
  651. if exists("s:prev_line") && s:prev_line != line('.')
  652. let s:columnize_count = 0
  653. endif
  654. let s:prev_line = line('.')
  655. " convert zero based indexed list to 1 based indexed list,
  656. " Default: 20 width, in case that column width isn't defined
  657. " Careful: Keep this fast! Using
  658. " let width=get(b:col_width,<SID>WColumn()-1,20)
  659. " is too slow, so we are using:
  660. let width=get(b:col_width, (s:columnize_count % s:max_cols), 20)
  661. let s:columnize_count += 1
  662. if !exists("g:csv_no_multibyte") &&
  663. \ match(a:field, '[^ -~]') != -1
  664. " match characters outside the ascii range
  665. let a = split(a:field, '\zs')
  666. let add = eval(join(map(a, 'len(v:val)'), '+'))
  667. let add -= len(a)
  668. else
  669. let add = 0
  670. endif
  671. " Add one for the frame
  672. " plus additional width for multibyte chars,
  673. " since printf(%*s..) uses byte width!
  674. let width = width + add + 1
  675. if width == strlen(a:field)
  676. " Column has correct length, don't use printf()
  677. return a:field
  678. else
  679. return printf("%*s", width , a:field)
  680. endif
  681. endfun
  682. fu! <sid>GetColPat(colnr, zs_flag) "{{{3
  683. " Return Pattern for given column
  684. if a:colnr > 1
  685. if !exists("b:csv_fixed_width_cols")
  686. let pat=b:col . '\{' . (a:colnr) . '\}'
  687. else
  688. if a:colnr >= len(b:csv_fixed_width_cols)
  689. " Get last column
  690. let pat='\%' . b:csv_fixed_width_cols[-1] . 'c.*'
  691. else
  692. let pat='\%' . b:csv_fixed_width_cols[(a:colnr - 1)] .
  693. \ 'c.\{-}\%' . b:csv_fixed_width_cols[a:colnr] . 'c'
  694. endif
  695. endif
  696. elseif !exists("b:csv_fixed_width_cols")
  697. let pat=b:col
  698. else
  699. let pat='\%' . b:csv_fixed_width_cols[0] . 'c.\{-}' .
  700. \ (len(b:csv_fixed_width_cols) > 1 ?
  701. \ '\%' . b:csv_fixed_width_cols[1] . 'c' :
  702. \ '')
  703. endif
  704. return pat . (a:zs_flag ? '\zs' : '')
  705. endfu
  706. fu! <sid>SplitHeaderLine(lines, bang, hor) "{{{3
  707. if exists("b:csv_fixed_width_cols")
  708. call <sid>Warn("Header does not work with fixed width column!")
  709. return
  710. endif
  711. " Check that there exists a header line
  712. call <sid>CheckHeaderLine()
  713. if !a:bang
  714. " A Split Header Window already exists,
  715. " first close the already existing Window
  716. if exists("b:csv_SplitWindow")
  717. call <sid>SplitHeaderLine(a:lines, 1, a:hor)
  718. endif
  719. " Split Window
  720. let _stl = &l:stl
  721. let _sbo = &sbo
  722. let a = []
  723. let b=b:col
  724. if a:hor
  725. setl scrollopt=hor scrollbind
  726. let lines = empty(a:lines) ? s:csv_fold_headerline : a:lines
  727. let a = getline(1,lines)
  728. " Does it make sense to use the preview window?
  729. " sil! pedit %
  730. sp +enew
  731. call setline(1, a)
  732. " Needed for syntax highlighting
  733. "let b:col=b
  734. "setl syntax=csv
  735. sil! doautocmd FileType csv
  736. 1
  737. exe "resize" . lines
  738. setl scrollopt=hor winfixheight nowrap
  739. "let &l:stl=repeat(' ', winwidth(0))
  740. let &l:stl="%#Normal#".repeat(' ',winwidth(0))
  741. else
  742. setl scrollopt=ver scrollbind
  743. 0
  744. let a=<sid>CopyCol('',1)
  745. " Force recalculating columns width
  746. unlet! b:csv_list
  747. try
  748. let width = <sid>ColWidth(1)
  749. catch /ColWidth/
  750. call <sid>Warn("Error: getting Column Width, using default!")
  751. endtry
  752. " Does it make sense to use the preview window?
  753. "vert sil! pedit |wincmd w | enew!
  754. abo vsp +enew
  755. call append(0, a)
  756. $d _
  757. sil %s/.*/\=printf("%.*s", width, submatch(0))/eg
  758. 0
  759. exe "vert res" width
  760. let b:col=b
  761. call matchadd("CSVHeaderLine", b:col)
  762. setl scrollopt=ver winfixwidth
  763. endif
  764. let win = winnr()
  765. setl scrollbind buftype=nowrite bufhidden=wipe noswapfile nobuflisted
  766. wincmd p
  767. let b:csv_SplitWindow = win
  768. aug CSV_Preview
  769. au!
  770. au BufWinLeave <buffer> call <sid>SplitHeaderLine(0, 1, 0)
  771. aug END
  772. else
  773. " Close split window
  774. if !exists("b:csv_SplitWindow")
  775. return
  776. endif
  777. exe b:csv_SplitWindow . "wincmd w"
  778. if exists("_stl")
  779. let &l:stl = _stl
  780. endif
  781. if exists("_sbo")
  782. let &sbo = _sbo
  783. endif
  784. setl noscrollbind
  785. wincmd c
  786. "pclose!
  787. unlet! b:csv_SplitWindow
  788. aug CSV_Preview
  789. au!
  790. aug END
  791. aug! CSV_Preview
  792. endif
  793. endfu
  794. fu! <sid>SplitHeaderToggle(hor) "{{{3
  795. if !exists("b:csv_SplitWindow")
  796. :call <sid>SplitHeaderLine(1,0,a:hor)
  797. else
  798. :call <sid>SplitHeaderLine(1,1,a:hor)
  799. endif
  800. endfu
  801. " TODO: from here on add logic for fixed-width csv files!
  802. fu! <sid>MoveCol(forward, line) "{{{3
  803. " Move cursor position upwards/downwards left/right
  804. let colnr=<SID>WColumn()
  805. let maxcol=<SID>MaxColumns()
  806. let cpos=getpos('.')[2]
  807. if !exists("b:csv_fixed_width_cols")
  808. call search(b:col, 'bc', line('.'))
  809. endif
  810. let spos=getpos('.')[2]
  811. " Check for valid column
  812. " a:forward == 1 : search next col
  813. " a:forward == -1: search prev col
  814. " a:forward == 0 : stay in col
  815. if colnr - v:count1 >= 1 && a:forward == -1
  816. let colnr -= v:count1
  817. elseif colnr - v:count1 < 1 && a:forward == -1
  818. let colnr = 0
  819. elseif colnr + v:count1 <= maxcol && a:forward == 1
  820. let colnr += v:count1
  821. elseif colnr + v:count1 > maxcol && a:forward == 1
  822. let colnr = maxcol + 1
  823. endif
  824. let line=a:line
  825. if line < 1
  826. let line=1
  827. elseif line > line('$')
  828. let line=line('$')
  829. endif
  830. " Generate search pattern
  831. if colnr == 1
  832. let pat = '^' . <SID>GetColPat(colnr-1,0)
  833. "let pat = pat . '\%' . line . 'l'
  834. elseif (colnr == 0) || (colnr == maxcol + 1)
  835. if !exists("b:csv_fixed_width_cols")
  836. let pat=b:col
  837. else
  838. if a:forward > 0
  839. " Move forwards
  840. let pat=<sid>GetColPat(1, 0)
  841. else
  842. " Move backwards
  843. let pat=<sid>GetColPat(maxcol, 0)
  844. endif
  845. endif
  846. else
  847. if !exists("b:csv_fixed_width_cols")
  848. let pat='^'. <SID>GetColPat(colnr-1,1) . b:col
  849. else
  850. let pat=<SID>GetColPat(colnr,0)
  851. endif
  852. "let pat = pat . '\%' . line . 'l'
  853. endif
  854. " Search
  855. " move left/right
  856. if a:forward > 0
  857. call search(pat, 'W')
  858. elseif a:forward < 0
  859. call search(pat, 'bWe')
  860. " Moving upwards/downwards
  861. elseif line >= line('.')
  862. call search(pat . '\%' . line . 'l', '', line)
  863. " Move to the correct screen column
  864. " This is a best effort approach, we might still
  865. " leave the column (if the next column is shorter)
  866. if !exists("b:csv_fixed_width_cols")
  867. let a = getpos('.')
  868. let a[2]+= cpos-spos
  869. else
  870. let a = getpos('.')
  871. let a[2] = cpos
  872. endif
  873. call setpos('.', a)
  874. elseif line < line('.')
  875. call search(pat . '\%' . line . 'l', 'b', line)
  876. " Move to the correct screen column
  877. if !exists("b:csv_fixed_width_cols")
  878. let a = getpos('.')
  879. let a[2]+= cpos-spos
  880. else
  881. let a = getpos('.')
  882. let a[2] = cpos
  883. endif
  884. call setpos('.', a)
  885. endif
  886. endfun
  887. fu! <sid>SortComplete(A,L,P) "{{{3
  888. return join(range(1,<sid>MaxColumns()),"\n")
  889. endfun
  890. fu! <sid>SortList(a1, a2) "{{{3
  891. return a:a1+0 == a:a2+0 ? 0 : a:a1+0 > a:a2+0 ? 1 : -1
  892. endfu
  893. fu! <sid>Sort(bang, line1, line2, colnr) range "{{{3
  894. let wsv=winsaveview()
  895. if a:colnr =~? 'n'
  896. let numeric = 1
  897. else
  898. let numeric = 0
  899. endif
  900. let col = (empty(a:colnr) || a:colnr !~? '\d\+') ? <sid>WColumn() : a:colnr+0
  901. if col != 1
  902. if !exists("b:csv_fixed_width_cols")
  903. let pat= '^' . <SID>GetColPat(col-1,1) . b:col
  904. else
  905. let pat= '^' . <SID>GetColPat(col,0)
  906. endif
  907. else
  908. let pat= '^' . <SID>GetColPat(col,0)
  909. endif
  910. exe a:line1 ',' a:line2 . "sort" . (a:bang ? '!' : '') .
  911. \' r ' . (numeric ? 'n' : '') . ' /' . pat . '/'
  912. call winrestview(wsv)
  913. endfun
  914. fu! CSV_WCol(...) "{{{3
  915. try
  916. if exists("a:1") && (a:1 == 'Name' || a:1 == 1)
  917. return printf("%s", <sid>WColumn(1))
  918. else
  919. return printf(" %d/%d", <SID>WColumn(), <SID>MaxColumns())
  920. endif
  921. catch
  922. return ''
  923. endtry
  924. endfun
  925. fu! <sid>CopyCol(reg, col) "{{{3
  926. " Return Specified Column into register reg
  927. let col = a:col == "0" ? <sid>WColumn() : a:col+0
  928. let mcol = <sid>MaxColumns()
  929. if col == '$' || col > mcol
  930. let col = mcol
  931. endif
  932. let a = []
  933. " Don't get lines, that are currently filtered away
  934. if !exists("b:csv_filter") || empty(b:csv_filter)
  935. let a=getline(1, '$')
  936. else
  937. for line in range(1, line('$'))
  938. if foldlevel(line)
  939. continue
  940. else
  941. call add(a, getline(line))
  942. endif
  943. endfor
  944. endif
  945. " Filter comments out
  946. let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
  947. call filter(a, 'v:val !~ pat')
  948. if !exists("b:csv_fixed_width_cols")
  949. call map(a, 'split(v:val, ''^'' . b:col . ''\zs'')[col-1]')
  950. else
  951. call map(a, 'matchstr(v:val, <sid>GetColPat(col, 0))')
  952. endif
  953. if a:reg =~ '[-"0-9a-zA-Z*+]'
  954. "exe ':let @' . a:reg . ' = "' . join(a, "\n") . '"'
  955. " set the register to blockwise mode
  956. call setreg(a:reg, join(a, "\n"), 'b')
  957. else
  958. return a
  959. endif
  960. endfu
  961. fu! <sid>MoveColumn(start, stop, ...) range "{{{3
  962. " Move column behind dest
  963. " Explicitly give the range as argument,
  964. " cause otherwise, Vim would move the cursor
  965. let wsv = winsaveview()
  966. let col = <sid>WColumn()
  967. let max = <sid>MaxColumns()
  968. " If no argument is given, move current column after last column
  969. let source=(exists("a:1") && a:1 > 0 && a:1 <= max ? a:1 : col)
  970. let dest =(exists("a:2") && a:2 > 0 && a:2 <= max ? a:2 : max)
  971. " translate 1 based columns into zero based list index
  972. let source -= 1
  973. let dest -= 1
  974. if source >= dest
  975. call <sid>Warn("Destination column before source column, aborting!")
  976. return
  977. endif
  978. " Swap line by line, instead of reading the whole range into memory
  979. for i in range(a:start, a:stop)
  980. let content = getline(i)
  981. if content =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
  982. " skip comments
  983. continue
  984. endif
  985. if !exists("b:csv_fixed_width_cols")
  986. let fields=split(content, b:col . '\zs')
  987. else
  988. let fields=[]
  989. for j in range(1, max, 1)
  990. call add(fields, matchstr(content, <sid>GetColPat(j,0)))
  991. endfor
  992. endif
  993. " Add delimiter to destination column, in case there was none,
  994. " remove delimiter from source, in case destination did not have one
  995. if matchstr(fields[dest], '.$') !~? b:delimiter
  996. let fields[dest] = fields[dest] . b:delimiter
  997. if matchstr(fields[source], '.$') =~? b:delimiter
  998. let fields[source] = substitute(fields[source],
  999. \ '^\(.*\).$', '\1', '')
  1000. endif
  1001. endif
  1002. let fields= (source == 0 ? [] : fields[0 : (source-1)])
  1003. \ + fields[ (source+1) : dest ]
  1004. \ + [ fields[source] ] + fields[(dest+1):]
  1005. call setline(i, join(fields, ''))
  1006. endfor
  1007. call winrestview(wsv)
  1008. endfu
  1009. fu! <sid>SumColumn(list) "{{{3
  1010. " Sum a list of values, but only consider the digits within each value
  1011. " parses the digits according to the given format (if none has been
  1012. " specified, assume POSIX format (without thousand separator) If Vim has
  1013. " does not support floats, simply sum up only the integer part
  1014. if empty(a:list)
  1015. return 0
  1016. else
  1017. let sum = has("float") ? 0.0 : 0
  1018. for item in a:list
  1019. if empty(item)
  1020. continue
  1021. endif
  1022. let nr = matchstr(item, '\d\(.*\d\)\?$')
  1023. let format1 = '^\d\+\zs\V' . s:nr_format[0] . '\m\ze\d'
  1024. let format2 = '\d\+\zs\V' . s:nr_format[1] . '\m\ze\d'
  1025. try
  1026. let nr = substitute(nr, format1, '', '')
  1027. if has("float") && s:nr_format[1] != '.'
  1028. let nr = substitute(nr, format2, '.', '')
  1029. endif
  1030. catch
  1031. let nr = 0
  1032. endtry
  1033. let sum += (has("float") ? str2float(nr) : (nr + 0))
  1034. endfor
  1035. if has("float")
  1036. if float2nr(sum) == sum
  1037. return float2nr(sum)
  1038. else
  1039. return printf("%.2f", sum)
  1040. endif
  1041. endif
  1042. return sum
  1043. endif
  1044. endfu
  1045. fu! csv#EvalColumn(nr, func, first, last) range "{{{3
  1046. let save = winsaveview()
  1047. call <sid>CheckHeaderLine()
  1048. let nr = matchstr(a:nr, '^\d\+')
  1049. let col = (empty(nr) ? <sid>WColumn() : nr)
  1050. " don't take the header line into consideration
  1051. let start = a:first - 1 + s:csv_fold_headerline
  1052. let stop = a:last - 1 + s:csv_fold_headerline
  1053. let column = <sid>CopyCol('', col)[start : stop]
  1054. " Delete delimiter
  1055. call map(column, 'substitute(v:val, b:delimiter . "$", "", "g")')
  1056. call map(column, 'substitute(v:val, ''^\s\+$'', "", "g")')
  1057. " Delete empty values
  1058. " Leave this up to the function that does something
  1059. " with each value
  1060. "call filter(column, '!empty(v:val)')
  1061. " parse the optional number format
  1062. let format = matchstr(a:nr, '/[^/]*/')
  1063. call <sid>NumberFormat()
  1064. if !empty(format)
  1065. try
  1066. let s = []
  1067. " parse the optional number format
  1068. let str = matchstr(format, '/\zs[^/]*\ze/', 0, start)
  1069. let s = matchlist(str, '\(.\)\?:\(.\)\?')[1:2]
  1070. if len(s) == 0
  1071. " Number format wrong
  1072. call <sid>Warn("Numberformat wrong, needs to be /x:y/!")
  1073. return ''
  1074. endif
  1075. if !empty(s[0])
  1076. let s:nr_format[0] = s[0]
  1077. endif
  1078. if !empty(s[1])
  1079. let s:nr_format[1] = s[1]
  1080. endif
  1081. endtry
  1082. endif
  1083. try
  1084. let result=call(function(a:func), [column])
  1085. return result
  1086. catch
  1087. " Evaluation of expression failed
  1088. echohl Title
  1089. echomsg "Evaluating" matchstr(a:func, '[a-zA-Z]\+$')
  1090. \ "failed for column" col . "!"
  1091. echohl Normal
  1092. return ''
  1093. finally
  1094. call winrestview(save)
  1095. endtry
  1096. endfu
  1097. fu! <sid>DoForEachColumn(start, stop, bang) range "{{{3
  1098. " Do something for each column,
  1099. " e.g. generate SQL-Statements, convert to HTML,
  1100. " something like this
  1101. " TODO: Define the function
  1102. " needs a csv_pre_convert variable
  1103. " csv_post_convert variable
  1104. " csv_convert variable
  1105. " result contains converted buffer content
  1106. let result = []
  1107. if !exists("g:csv_convert")
  1108. call <sid>Warn("You need to define how to convert your data using" .
  1109. \ "the g:csv_convert variable, see :h csv-convert")
  1110. return
  1111. endif
  1112. if exists("g:csv_pre_convert") && !empty(g:csv_pre_convert)
  1113. call add(result, g:csv_pre_convert)
  1114. endif
  1115. for item in range(a:start, a:stop, 1)
  1116. let t = g:csv_convert
  1117. let line = getline(item)
  1118. if line =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
  1119. " Filter comments out
  1120. call add(result, line)
  1121. continue
  1122. endif
  1123. let context = split(g:csv_convert, '%s')
  1124. let columns = len(context)
  1125. if columns > <sid>MaxColumns()
  1126. let columns = <sid>MaxColumns()
  1127. elseif columns == 1
  1128. call <sid>Warn("No Columns defined in your g:csv_convert variable, Aborting")
  1129. return
  1130. endif
  1131. if !exists("b:csv_fixed_width_cols")
  1132. let fields=split(line, b:col . '\zs')
  1133. if a:bang
  1134. call map(fields, 'substitute(v:val, b:delimiter .
  1135. \ ''\?$'' , "", "")')
  1136. endif
  1137. else
  1138. let fields=[]
  1139. for j in range(1, columns, 1)
  1140. call add(fields, matchstr(line, <sid>GetColPat(j,0)))
  1141. endfor
  1142. endif
  1143. for j in range(1, columns, 1)
  1144. let t=substitute(t, '%s', fields[j-1], '')
  1145. endfor
  1146. call add(result, t)
  1147. endfor
  1148. if exists("g:csv_post_convert") && !empty(g:csv_post_convert)
  1149. call add(result, g:csv_post_convert)
  1150. endif
  1151. new
  1152. call append('$', result)
  1153. 1d _
  1154. endfun
  1155. fu! <sid>PrepareDoForEachColumn(start, stop, bang) range"{{{3
  1156. let pre = exists("g:csv_pre_convert") ? g:csv_pre_convert : ''
  1157. let g:csv_pre_convert=input('Pre convert text: ', pre)
  1158. let post = exists("g:csv_post_convert") ? g:csv_post_convert : ''
  1159. let g:csv_post_convert=input('Post convert text: ', post)
  1160. let convert = exists("g:csv_convert") ? g:csv_convert : ''
  1161. let g:csv_convert=input("Converted text, use %s for column input:\n", convert)
  1162. call <sid>DoForEachColumn(a:start, a:stop, a:bang)
  1163. endfun
  1164. fu! <sid>EscapeValue(val) "{{{3
  1165. return '\V' . escape(a:val, '\')
  1166. endfu
  1167. fu! <sid>FoldValue(lnum, filter) "{{{3
  1168. call <sid>CheckHeaderLine()
  1169. if (a:lnum == s:csv_fold_headerline)
  1170. " Don't fold away the header line
  1171. return 0
  1172. endif
  1173. let result = 0
  1174. for item in values(a:filter)
  1175. " always fold comments away
  1176. let content = getline(a:lnum)
  1177. if content =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
  1178. return 1
  1179. elseif eval('content' . (item.match ? '!~' : '=~') . 'item.pat')
  1180. let result += 1
  1181. endif
  1182. endfor
  1183. return (result > 0)
  1184. endfu
  1185. fu! <sid>PrepareFolding(add, match) "{{{3
  1186. if !has("folding")
  1187. return
  1188. endif
  1189. " Move folded-parts away?
  1190. if exists("g:csv_move_folds")
  1191. let s:csv_move_folds = g:csv_move_folds
  1192. else
  1193. let s:csv_move_folds = 0
  1194. endif
  1195. if !exists("b:csv_filter")
  1196. let b:csv_filter = {}
  1197. endif
  1198. if !exists("s:filter_count") || s:filter_count < 1
  1199. let s:filter_count = 0
  1200. endif
  1201. let cpos = winsaveview()
  1202. if !a:add
  1203. " remove last added item from filter
  1204. if len(b:csv_filter) > 0
  1205. call <sid>RemoveLastItem(s:filter_count)
  1206. let s:filter_count -= 1
  1207. if len(b:csv_filter) == 0
  1208. call <sid>DisableFolding()
  1209. return
  1210. endif
  1211. else
  1212. " Disable folding, if no pattern available
  1213. call <sid>DisableFolding()
  1214. return
  1215. endif
  1216. else
  1217. let col = <sid>WColumn()
  1218. let max = <sid>MaxColumns()
  1219. let a = <sid>GetColumn(line('.'), col)
  1220. if !exists("b:csv_fixed_width")
  1221. try
  1222. " strip leading whitespace
  1223. if (a =~ '\s\+'. b:delimiter . '$')
  1224. let b = split(a, '^\s\+\ze[^' . b:delimiter. ']\+')[0]
  1225. else
  1226. let b = a
  1227. endif
  1228. catch /^Vim\%((\a\+)\)\=:E684/
  1229. " empty pattern - should match only empty columns
  1230. let b = a
  1231. endtry
  1232. " strip trailing delimiter
  1233. try
  1234. let a = split(b, b:delimiter . '$')[0]
  1235. catch /^Vim\%((\a\+)\)\=:E684/
  1236. let a = b
  1237. endtry
  1238. if a == b:delimiter
  1239. try
  1240. let a=repeat(' ', <sid>ColWidth(col))
  1241. catch
  1242. " no-op
  1243. endtry
  1244. endif
  1245. endif
  1246. " Make a column pattern
  1247. let b= '\%(' .
  1248. \ (exists("b:csv_fixed_width") ? '.*' : '') .
  1249. \ <sid>GetPat(col, max, <sid>EscapeValue(a) . '\m') .
  1250. \ '\)'
  1251. let s:filter_count += 1
  1252. let b:csv_filter[s:filter_count] = { 'pat': b, 'id': s:filter_count,
  1253. \ 'col': col, 'orig': a, 'match': a:match}
  1254. endif
  1255. " Put the pattern into the search register, so they will also
  1256. " be highlighted
  1257. " let @/ = ''
  1258. " for val in sort(values(b:csv_filter), '<sid>SortFilter')
  1259. " let @/ .= val.pat . (val.id == s:filter_count ? '' : '\&')
  1260. " endfor
  1261. let sid = <sid>GetSID()
  1262. " Fold settings:
  1263. call <sid>LocalSettings('fold')
  1264. " Don't put spaces between the arguments!
  1265. exe 'setl foldexpr=<snr>' . sid . '_FoldValue(v:lnum,b:csv_filter)'
  1266. " Move folded area to the bottom, so there is only on consecutive
  1267. " non-folded area
  1268. if exists("s:csv_move_folds") && s:csv_move_folds
  1269. \ && !&l:ro && &l:ma
  1270. folddoclosed m$
  1271. let cpos.lnum = s:csv_fold_headerline + 1
  1272. endif
  1273. call winrestview(cpos)
  1274. endfu
  1275. fu! <sid>OutputFilters(bang) "{{{3
  1276. if !a:bang
  1277. call <sid>CheckHeaderLine()
  1278. if s:csv_fold_headerline
  1279. let title="Nr\tMatch\tCol\t Name\tValue"
  1280. else
  1281. let title="Nr\tMatch\tCol\tValue"
  1282. endif
  1283. echohl "Title"
  1284. echo printf("%s", title)
  1285. echo printf("%s", repeat("=",strdisplaywidth(title)))
  1286. echohl "Normal"
  1287. if !exists("b:csv_filter") || len(b:csv_filter) == 0
  1288. echo printf("%s", "No active filter")
  1289. else
  1290. let items = values(b:csv_filter)
  1291. call sort(items, "<sid>SortFilter")
  1292. for item in items
  1293. if s:csv_fold_headerline
  1294. echo printf("%02d\t% 2s\t%02d\t%10.10s\t%s",
  1295. \ item.id, (item.match ? '+' : '-'), item.col,
  1296. \ substitute(<sid>GetColumn(1, item.col),
  1297. \ b:col.'$', '', ''), item.orig)
  1298. else
  1299. echo printf("%02d\t% 2s\t%02d\t%s",
  1300. \ item.id, (item.match ? '+' : '-'),
  1301. \ item.col, item.orig)
  1302. endif
  1303. endfor
  1304. endif
  1305. else
  1306. " Reapply filter again
  1307. if !exists("b:csv_filter") || len(b:csv_filter) == 0
  1308. call <sid>Warn("No filters defined currently!")
  1309. return
  1310. else
  1311. let sid = <sid>GetSID()
  1312. exe 'setl foldexpr=<snr>' . sid . '_FoldValue(v:lnum,b:csv_filter)'
  1313. endif
  1314. endif
  1315. endfu
  1316. fu! <sid>SortFilter(a, b) "{{{3
  1317. return a:a.id == a:b.id ? 0 :
  1318. \ a:a.id > a:b.id ? 1 : -1
  1319. endfu
  1320. fu! <sid>GetColumn(line, col) "{{{3
  1321. " Return Column content at a:line, a:col
  1322. let a=getline(a:line)
  1323. " Filter comments out
  1324. if a =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
  1325. return ''
  1326. endif
  1327. if !exists("b:csv_fixed_width_cols")
  1328. try
  1329. let a = split(a, '^' . b:col . '\zs')[a:col - 1]
  1330. catch
  1331. " index out of range
  1332. let a = ''
  1333. endtry
  1334. else
  1335. let a = matchstr(a, <sid>GetColPat(a:col, 0))
  1336. endif
  1337. return substitute(a, '^\s\+\|\s\+$', '', 'g')
  1338. endfu
  1339. fu! <sid>RemoveLastItem(count) "{{{3
  1340. for [key,value] in items(b:csv_filter)
  1341. if value.id == a:count
  1342. call remove(b:csv_filter, key)
  1343. endif
  1344. endfor
  1345. endfu
  1346. fu! <sid>DisableFolding() "{{{3
  1347. setl nofen fdm=manual fdc=0 fdl=0 fillchars+=fold:-
  1348. endfu
  1349. fu! <sid>GetSID() "{{{3
  1350. if v:version > 703 || v:version == 703 && has("patch032")
  1351. return maparg('W', "", "", 1).sid
  1352. else
  1353. "return substitute(maparg('W'), '\(<SNR>\d\+\)_', '\1', '')
  1354. return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_GetSID$')
  1355. endif
  1356. endfu
  1357. fu! <sid>NumberFormat() "{{{3
  1358. let s:nr_format = [',', '.']
  1359. if exists("b:csv_thousands_sep")
  1360. let s:nr_format[0] = b:csv_thousands_sep
  1361. endif
  1362. if exists("b:csv_decimal_sep")
  1363. let s:nr_format[1] = b:csv_decimal_sep
  1364. endif
  1365. endfu
  1366. fu! <sid>CheckHeaderLine() "{{{3
  1367. if !exists("b:csv_headerline")
  1368. let s:csv_fold_headerline = 1
  1369. else
  1370. let s:csv_fold_headerline = b:csv_headerline
  1371. endif
  1372. endfu
  1373. fu! <sid>AnalyzeColumn(...) "{{{3
  1374. let maxcolnr = <SID>MaxColumns()
  1375. if len(a:000) == 1
  1376. let colnr = a:1
  1377. else
  1378. let colnr = <sid>WColumn()
  1379. endif
  1380. if colnr > maxcolnr
  1381. call <SID>Warn("There exists no column " . colnr)
  1382. return 1
  1383. endif
  1384. " Initialize s:fold_headerline
  1385. call <sid>CheckHeaderLine()
  1386. let data = <sid>CopyCol('', colnr)[s:csv_fold_headerline : -1]
  1387. let qty = len(data)
  1388. let res = {}
  1389. for item in data
  1390. if !get(res, item)
  1391. let res[item] = 0
  1392. endif
  1393. let res[item]+=1
  1394. endfor
  1395. let max_items = reverse(sort(values(res)))
  1396. if len(max_items) > 5
  1397. call remove(max_items, 5, -1)
  1398. call filter(res, 'v:val =~ ''^''.join(max_items, ''\|'').''$''')
  1399. endif
  1400. if has("float")
  1401. let title="Nr\tCount\t % \tValue"
  1402. else
  1403. let title="Nr\tCount\tValue"
  1404. endif
  1405. echohl "Title"
  1406. echo printf("%s", title)
  1407. echohl "Normal"
  1408. echo printf("%s", repeat('=', strdisplaywidth(title)))
  1409. let i=1
  1410. for val in max_items
  1411. for key in keys(res)
  1412. if res[key] == val && i <= len(max_items)
  1413. if !empty(b:delimiter)
  1414. let k = substitute(key, b:delimiter . '\?$', '', '')
  1415. else
  1416. let k = key
  1417. endif
  1418. if has("float")
  1419. echo printf("%02d\t%02d\t%2.0f%%\t%.50s", i, res[key],
  1420. \ ((res[key] + 0.0)/qty)*100, k)
  1421. else
  1422. echo printf("%02d\t%02d\t%.50s", i, res[key], k)
  1423. endif
  1424. call remove(res,key)
  1425. let i+=1
  1426. else
  1427. continue
  1428. endif
  1429. endfor
  1430. endfor
  1431. unlet max_items
  1432. endfunc
  1433. fu! <sid>Vertfold(bang, col) "{{{3
  1434. if a:bang
  1435. do Syntax
  1436. return
  1437. endif
  1438. if !has("conceal")
  1439. call <sid>Warn("Concealing not supported in your Vim")
  1440. return
  1441. endif
  1442. if empty(b:delimiter) && !exists("b:csv_fixed_width_cols")
  1443. call <sid>Warn("There are no columns defined, can't hide away anything!")
  1444. return
  1445. endif
  1446. if empty(a:col)
  1447. let colnr=<SID>WColumn()
  1448. else
  1449. let colnr=a:col
  1450. endif
  1451. let pat=<sid>GetPat(colnr, <sid>MaxColumns(), '.*')
  1452. if exists("b:csv_fixed_width_cols") &&
  1453. \ pat !~ '^\^\.\*'
  1454. " Make the pattern implicitly start at line start,
  1455. " so it will be applied by syntax highlighting (:h :syn-priority)
  1456. let pat='^.*' . pat
  1457. endif
  1458. let pat=substitute(pat, '\\zs\(\.\*\)\@=', '', '')
  1459. if !empty(pat)
  1460. exe "syn match CSVFold /" . pat . "/ conceal cchar=+"
  1461. endif
  1462. endfu
  1463. fu! <sid>InitCSVFixedWidth() "{{{3
  1464. if !exists("+cc")
  1465. " TODO: make this work with a custom matchadd() command for older
  1466. " Vims, that don't have 'colorcolumn'
  1467. call <sid>Warn("'colorcolumn' option not available")
  1468. return
  1469. endif
  1470. " Turn off syntax highlighting
  1471. syn clear
  1472. let _cc = &l:cc
  1473. let &l:cc = 1
  1474. redraw!
  1475. let list = []
  1476. let tcc = &l:cc
  1477. echo "<Cursor>, <Space>, <ESC>, <BS>, <CR>..."
  1478. let char=getchar()
  1479. while 1
  1480. if char == "\<Left>" || char == "\<Right>"
  1481. let tcc = eval('tcc'.(char=="\<Left>" ? '-' : '+').'1')
  1482. elseif char == "\<Space>" || char == 32 " Space
  1483. call add(list, tcc)
  1484. elseif char == "\<BS>" || char == 127
  1485. call remove(list, -1)
  1486. elseif char == "\<ESC>" || char == 27
  1487. let &l:cc=_cc
  1488. redraw!
  1489. return
  1490. else
  1491. break
  1492. endif
  1493. let &l:cc=tcc . (!empty(list)? ',' . join(list, ','):'')
  1494. redraw!
  1495. echo "<Cursor>, <Space>, <ESC>, <BS>, <CR>..."
  1496. let char=getchar()
  1497. endw
  1498. if tcc > 0
  1499. call add(list,tcc)
  1500. endif
  1501. let b:csv_fixed_width_cols=[]
  1502. let tcc=0
  1503. if !empty(list)
  1504. call Break()
  1505. " Remove duplicate entries
  1506. for val in sort(list, "<sid>SortList")
  1507. if val==tcc
  1508. continue
  1509. endif
  1510. call add(b:csv_fixed_width_cols, val)
  1511. let tcc=val
  1512. endfor
  1513. let b:csv_fixed_width=join(sort(b:csv_fixed_width_cols,
  1514. \ "<sid>SortList"), ',')
  1515. call <sid>Init()
  1516. endif
  1517. let &l:cc=_cc
  1518. redraw!
  1519. endfu
  1520. fu! Break()
  1521. return
  1522. endfu
  1523. fu! <sid>NewRecord(line1, line2, count) "{{{3
  1524. if a:count =~ "\D"
  1525. call <sid>WarningMsg("Invalid count specified")
  1526. return
  1527. endif
  1528. let cnt = (empty(a:count) ? 1 : a:count)
  1529. let record = ""
  1530. for item in range(1,<sid>MaxColumns())
  1531. if !exists("b:col_width")
  1532. " Best guess width
  1533. if exists("b:csv_fixed_width_cols")
  1534. let record .= printf("%*s", <sid>ColWidth(item),
  1535. \ b:delimiter)
  1536. else
  1537. let record .= printf("%20s", b:delimiter)
  1538. endif
  1539. else
  1540. let record .= printf("%*s", b:col_width[item-1]+1, b:delimiter)
  1541. endif
  1542. endfor
  1543. if getline(1)[-1:] != b:delimiter
  1544. let record = record[0:-2] . " "
  1545. endif
  1546. let line = []
  1547. for item in range(cnt)
  1548. call add(line, record)
  1549. endfor
  1550. for nr in range(a:line1, a:line2)
  1551. call append(nr, line)
  1552. endfor
  1553. endfu
  1554. fu! <sid>MoveOver(outer) "{{{3
  1555. " Move over a field
  1556. " a:outer means include the delimiter
  1557. let last = 0
  1558. let mode = a:outer
  1559. if <sid>WColumn() == <sid>MaxColumns()
  1560. let last = 1
  1561. if !mode && getline('.')[-1:] != b:delimiter
  1562. " No trailing delimiter, so inner == outer
  1563. let mode = 1
  1564. endif
  1565. endif
  1566. " Use the mapped key
  1567. exe ":sil! norm E"
  1568. let _s = @/
  1569. if last
  1570. exe "sil! norm! /" . b:col . "\<cr>v$h" . (mode ? "" : "\<Left>")
  1571. else
  1572. exe "sil! norm! /" . b:col . "\<cr>vn\<Left>" . (mode ? "" : "\<Left>")
  1573. endif
  1574. let @/ = _s
  1575. endfu
  1576. fu! <sid>CSVMappings() "{{{3
  1577. call <sid>Map('noremap', 'W', ':<C-U>call <SID>MoveCol(1, line("."))<CR>')
  1578. call <sid>Map('noremap', 'E', ':<C-U>call <SID>MoveCol(-1, line("."))<CR>')
  1579. call <sid>Map('noremap', 'K', ':<C-U>call <SID>MoveCol(0,
  1580. \ line(".")-v:count1)<CR>')
  1581. call <sid>Map('noremap', 'J', ':<C-U>call <SID>MoveCol(0,
  1582. \ line(".")+v:count1)<CR>')
  1583. call <sid>Map('nnoremap', '<CR>', ':<C-U>call <SID>PrepareFolding(1,
  1584. \ 1)<CR>')
  1585. call <sid>Map('nnoremap', '<Space>', ':<C-U>call <SID>PrepareFolding(1,
  1586. \ 0)<CR>')
  1587. call <sid>Map('nnoremap', '<BS>', ':<C-U>call <SID>PrepareFolding(0,
  1588. \ 1)<CR>')
  1589. " Text object: Field
  1590. call <sid>Map('vnoremap', 'if', ':<C-U>call <sid>MoveOver(0)<CR>')
  1591. call <sid>Map('vnoremap', 'af', ':<C-U>call <sid>MoveOver(1)<CR>')
  1592. call <sid>Map('omap', 'af', ':norm vaf<cr>')
  1593. call <sid>Map('omap', 'if', ':norm vif<cr>')
  1594. " Remap <CR> original values to a sane backup
  1595. call <sid>Map('noremap', '<LocalLeader>J', 'J')
  1596. call <sid>Map('noremap', '<LocalLeader>K', 'K')
  1597. call <sid>Map('vnoremap', '<LocalLeader>W', 'W')
  1598. call <sid>Map('vnoremap', '<LocalLeader>E', 'E')
  1599. call <sid>Map('noremap', '<LocalLeader>H', 'H')
  1600. call <sid>Map('noremap', '<LocalLeader>L', 'L')
  1601. call <sid>Map('nnoremap', '<LocalLeader><CR>', '<CR>')
  1602. call <sid>Map('nnoremap', '<LocalLeader><Space>', '<Space>')
  1603. call <sid>Map('nnoremap', '<LocalLeader><BS>', '<BS>')
  1604. call <sid>Map('map', '<C-Right>', 'W')
  1605. call <sid>Map('map', '<C-Left>', 'E')
  1606. call <sid>Map('map', 'H', 'E')
  1607. call <sid>Map('map', 'L', 'W')
  1608. call <sid>Map('map', '<Up>', 'K')
  1609. call <sid>Map('map', '<Down>', 'J')
  1610. endfu
  1611. fu! <sid>CommandDefinitions() "{{{3
  1612. call <sid>LocalCmd("WhatColumn", ':echo <sid>WColumn(<bang>0)',
  1613. \ '-bang')
  1614. call <sid>LocalCmd("NrColumns", ':call <sid>NrColumns(<q-bang>)', '-bang')
  1615. call <sid>LocalCmd("HiColumn", ':call <sid>HiCol(<q-args>,<bang>0)',
  1616. \ '-bang -nargs=?')
  1617. call <sid>LocalCmd("SearchInColumn",
  1618. \ ':call <sid>SearchColumn(<q-args>)', '-nargs=*')
  1619. call <sid>LocalCmd("DeleteColumn", ':call <sid>DeleteColumn(<q-args>)',
  1620. \ '-nargs=? -complete=custom,<sid>SortComplete')
  1621. call <sid>LocalCmd("ArrangeColumn",
  1622. \ ':call <sid>ArrangeCol(<line1>, <line2>, <bang>0)',
  1623. \ '-range -bang')
  1624. call <sid>LocalCmd("UnArrangeColumn",
  1625. \':call <sid>PrepUnArrangeCol(<line1>, <line2>)',
  1626. \ '-range')
  1627. call <sid>LocalCmd("InitCSV", ':call <sid>Init()', '')
  1628. call <sid>LocalCmd('Header',
  1629. \ ':call <sid>SplitHeaderLine(<q-args>,<bang>0,1)',
  1630. \ '-nargs=? -bang')
  1631. call <sid>LocalCmd('VHeader',
  1632. \ ':call <sid>SplitHeaderLine(<q-args>,<bang>0,0)',
  1633. \ '-nargs=? -bang')
  1634. call <sid>LocalCmd("HeaderToggle",
  1635. \ ':call <sid>SplitHeaderToggle(1)', '')
  1636. call <sid>LocalCmd("VHeaderToggle",
  1637. \ ':call <sid>SplitHeaderToggle(0)', '')
  1638. call <sid>LocalCmd("Sort",
  1639. \ ':call <sid>Sort(<bang>0, <line1>,<line2>,<q-args>)',
  1640. \ '-nargs=* -bang -range=% -complete=custom,<sid>SortComplete')
  1641. call <sid>LocalCmd("Column",
  1642. \ ':call <sid>CopyCol(empty(<q-reg>)?''"'':<q-reg>,<q-count>)',
  1643. \ '-count -register')
  1644. call <sid>LocalCmd("MoveColumn",
  1645. \ ':call <sid>MoveColumn(<line1>,<line2>,<f-args>)',
  1646. \ '-range=% -nargs=* -complete=custom,<sid>SortComplete')
  1647. call <sid>LocalCmd("SumCol",
  1648. \ ':echo csv#EvalColumn(<q-args>, "<sid>SumColumn", <line1>,<line2>)',
  1649. \ '-nargs=? -range=% -complete=custom,<sid>SortComplete')
  1650. call <sid>LocalCmd("ConvertData",
  1651. \ ':call <sid>PrepareDoForEachColumn(<line1>,<line2>,<bang>0)',
  1652. \ '-bang -nargs=? -range=%')
  1653. call <sid>LocalCmd("Filters", ':call <sid>OutputFilters(<bang>0)',
  1654. \ '-nargs=0 -bang')
  1655. call <sid>LocalCmd("Analyze", ':call <sid>AnalyzeColumn(<args>)',
  1656. \ '-nargs=?')
  1657. call <sid>LocalCmd("VertFold", ':call <sid>Vertfold(<bang>0,<q-args>)',
  1658. \ '-bang -nargs=? -range=% -complete=custom,<sid>SortComplete')
  1659. call <sid>LocalCmd("CSVFixed", ':call <sid>InitCSVFixedWidth()', '')
  1660. call <sid>LocalCmd("NewRecord", ':call <sid>NewRecord(<line1>,
  1661. \ <line2>, <q-args>)', '-nargs=? -range')
  1662. call <sid>LocalCmd("NewDelimiter", ':call <sid>NewDelimiter(<q-args>)',
  1663. \ '-nargs=1')
  1664. call <sid>LocalCmd("Duplicates", ':call <sid>CheckDuplicates(<q-args>)',
  1665. \ '-nargs=1 -complete=custom,<sid>CompleteColumnNr')
  1666. call <sid>LocalCmd('Transpose', ':call <sid>Transpose(<line1>, <line2>)',
  1667. \ '-range=%')
  1668. call <sid>LocalCmd('Tabularize', ':call <sid>Tabularize()','')
  1669. endfu
  1670. fu! <sid>Map(map, name, definition) "{{{3
  1671. " All mappings are buffer local
  1672. exe a:map "<buffer> <silent>" a:name a:definition
  1673. " should already exists
  1674. if a:map == 'nnoremap'
  1675. let unmap = 'nunmap'
  1676. elseif a:map == 'noremap' || a:map == 'map'
  1677. let unmap = 'unmap'
  1678. elseif a:map == 'vnoremap'
  1679. let unmap = 'vunmap'
  1680. elseif a:map == 'omap'
  1681. let unmap = 'ounmap'
  1682. endif
  1683. let b:undo_ftplugin .= "| " . unmap . " <buffer> " . a:name
  1684. endfu
  1685. fu! <sid>LocalCmd(name, definition, args) "{{{3
  1686. if !exists(':'.a:name)
  1687. exe "com! -buffer " a:args a:name a:definition
  1688. let b:undo_ftplugin .= "| sil! delc " . a:name
  1689. endif
  1690. endfu
  1691. fu! <sid>Menu(enable) "{{{3
  1692. if a:enable
  1693. " Make a menu for the graphical vim
  1694. amenu CSV.&Init\ Plugin :InitCSV<cr>
  1695. amenu CSV.SetUp\ &fixedwidth\ Cols :CSVFixed<cr>
  1696. amenu CSV.-sep1- <nul>
  1697. amenu &CSV.&Column.&Number :WhatColumn<cr>
  1698. amenu CSV.Column.N&ame :WhatColumn!<cr>
  1699. amenu CSV.Column.&Highlight\ column :HiColumn<cr>
  1700. amenu CSV.Column.&Remove\ highlight :HiColumn!<cr>
  1701. amenu CSV.Column.&Delete :DeleteColumn<cr>
  1702. amenu CSV.Column.&Sort :%Sort<cr>
  1703. amenu CSV.Column.&Copy :Column<cr>
  1704. amenu CSV.Column.&Move :%MoveColumn<cr>
  1705. amenu CSV.Column.S&um :%SumCol<cr>
  1706. amenu CSV.Column.Analy&ze :Analyze<cr>
  1707. amenu CSV.Column.&Arrange :%ArrangeCol<cr>
  1708. amenu CSV.Column.&UnArrange :%UnArrangeCol<cr>
  1709. amenu CSV.-sep2- <nul>
  1710. amenu CSV.&Toggle\ Header :HeaderToggle<cr>
  1711. amenu CSV.&ConvertData :ConvertData<cr>
  1712. amenu CSV.Filters :Filters<cr>
  1713. amenu CSV.Hide\ C&olumn :VertFold<cr>
  1714. amenu CSV.&New\ Record :NewRecord<cr>
  1715. else
  1716. " just in case the Menu wasn't defined properly
  1717. sil! amenu disable CSV
  1718. endif
  1719. endfu
  1720. fu! <sid>SaveOptions(list) "{{{3
  1721. let save = {}
  1722. for item in a:list
  1723. exe "let save.". item. " = &l:". item
  1724. endfor
  1725. return save
  1726. endfu
  1727. fu! <sid>NewDelimiter(newdelimiter) "{{{3
  1728. let save = <sid>SaveOptions(['ro', 'ma'])
  1729. if exists("b:csv_fixed_width_cols")
  1730. call <sid>Warn("NewDelimiter does not work with fixed width column!")
  1731. return
  1732. endif
  1733. if !&l:ma
  1734. setl ma
  1735. endif
  1736. if &l:ro
  1737. setl noro
  1738. endif
  1739. let line=1
  1740. while line <= line('$')
  1741. " Don't change delimiter for comments
  1742. if getline(line) =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
  1743. let line+=1
  1744. continue
  1745. endif
  1746. let fields=split(getline(line), b:col . '\zs')
  1747. " Remove field delimiter
  1748. call map(fields, 'substitute(v:val, b:delimiter .
  1749. \ ''\?$'' , "", "")')
  1750. call setline(line, join(fields, a:newdelimiter))
  1751. let line+=1
  1752. endwhile
  1753. " reset local buffer options
  1754. for [key, value] in items(save)
  1755. call setbufvar('', '&'. key, value)
  1756. endfor
  1757. "reinitialize the plugin
  1758. call <sid>Init()
  1759. endfu
  1760. fu! <sid>IN(list, value) "{{{3
  1761. for item in a:list
  1762. if item == a:value
  1763. return 1
  1764. endif
  1765. endfor
  1766. return 0
  1767. endfu
  1768. fu! <sid>DuplicateRows(columnlist) "{{{3
  1769. let duplicates = {}
  1770. let cnt = 0
  1771. let line = 1
  1772. while line <= line('$')
  1773. let key = ""
  1774. let i = 1
  1775. let content = getline(line)
  1776. " Skip comments
  1777. if content =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
  1778. continue
  1779. endif
  1780. let cols = split(content, b:col. '\zs')
  1781. for column in cols
  1782. if <sid>IN(a:columnlist, i)
  1783. let key .= column
  1784. endif
  1785. let i += 1
  1786. endfor
  1787. if has_key(duplicates, key) && cnt < 10
  1788. call <sid>Warn("Duplicate Row ". line)
  1789. let cnt += 1
  1790. elseif has_key(duplicates, key)
  1791. call <sid>Warn("More duplicate Rows after: ". line)
  1792. call <sid>Warn("Aborting...")
  1793. return
  1794. else
  1795. let duplicates[key] = 1
  1796. endif
  1797. let line += 1
  1798. endwhile
  1799. if cnt == 0
  1800. call <sid>Warn("No Duplicate Row found!")
  1801. endif
  1802. endfu
  1803. fu! <sid>CompleteColumnNr(A,L,P) "{{{3
  1804. return join(range(1,<sid>MaxColumns()), "\n")
  1805. endfu
  1806. fu! <sid>CheckDuplicates(list) "{{{3
  1807. let string = a:list
  1808. if string =~ '\d\s\?-\s\?\d'
  1809. let string = substitute(string, '\(\d\+\)\s\?-\s\?\(\d\+\)',
  1810. \ '\=join(range(submatch(1),submatch(2)), ",")', '')
  1811. endif
  1812. let list=split(string, ',')
  1813. call <sid>DuplicateRows(list)
  1814. endfu
  1815. fu! <sid>Transpose(line1, line2) "{{{3
  1816. " Note: - Comments will be deleted.
  1817. " - Does not work with fixed-width columns
  1818. if exists("b:csv_fixed_width")
  1819. call <sid>Warn("Transposing does not work with fixed-width columns!")
  1820. return
  1821. endif
  1822. let _wsv = winsaveview()
  1823. let TrailingDelim = 0
  1824. if line('$') > 1
  1825. let TrailingDelim = getline(1) =~ b:delimiter.'$'
  1826. endif
  1827. let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
  1828. try
  1829. let columns = <sid>MaxColumns(a:line1)
  1830. catch
  1831. " No column, probably because of comment or empty line
  1832. " so use the number of columns from the beginning of the file
  1833. let columns = <sid>MaxColumns()
  1834. endtry
  1835. let matrix = []
  1836. for line in range(a:line1, a:line2)
  1837. " Filter comments out
  1838. if getline(line) =~ pat
  1839. continue
  1840. endif
  1841. let r = []
  1842. for row in range(1,columns)
  1843. let field = <sid>GetColumn(line, row)
  1844. call add(r, field)
  1845. endfor
  1846. call add(matrix, r)
  1847. endfor
  1848. unlet row
  1849. " create new transposed matrix
  1850. let transposed = []
  1851. for row in matrix
  1852. let i = 0
  1853. for val in row
  1854. if get(transposed, i, []) == []
  1855. call add(transposed, [])
  1856. endif
  1857. if val[-1:] != b:delimiter
  1858. let val .= b:delimiter
  1859. endif
  1860. call add(transposed[i], val)
  1861. let i+=1
  1862. endfor
  1863. endfor
  1864. " Save memory
  1865. unlet! matrix
  1866. call map(transposed, 'join(v:val, '''')')
  1867. if !TrailingDelim
  1868. call map(transposed, 'substitute(v:val, b:delimiter.''\?$'', "", "")')
  1869. endif
  1870. " filter out empty records
  1871. call filter(transposed, 'v:val != b:delimiter')
  1872. " Insert transposed data
  1873. let delete_last_line = 0
  1874. if a:line1 == 1 && a:line2 == line('$')
  1875. let delete_last_line = 1
  1876. endif
  1877. exe a:line1. ",". a:line2. "d _"
  1878. let first = (a:line1 > 0 ? (a:line1 - 1) : 0)
  1879. call append(first, transposed)
  1880. if delete_last_line
  1881. sil $d _
  1882. endif
  1883. " save memory
  1884. unlet! transposed
  1885. call winrestview(_wsv)
  1886. endfu
  1887. fu! <sid>NrColumns(bang) "{{{3
  1888. if !empty(a:bang)
  1889. try
  1890. let cols = <sid>MaxColumns(line('.'))
  1891. catch
  1892. " No column or comment line
  1893. call <sid>Warn("No valid CSV Column!")
  1894. endtry
  1895. else
  1896. let cols = <sid>MaxColumns()
  1897. endif
  1898. echo cols
  1899. endfu
  1900. fu! <sid>Tabularize() "{{{3
  1901. let _c = winsaveview()
  1902. let _ma = &l:ma
  1903. setl ma
  1904. call <sid>CheckHeaderLine()
  1905. if exists("b:csv_fixed_width_cols")
  1906. let cols=copy(b:csv_fixed_width_cols)
  1907. let pat = join(map(cols, ' ''\(\%''. v:val. ''c\)'' '), '\|')
  1908. let colwidth = strlen(substitute(getline('$'), '.', 'x', 'g'))
  1909. else
  1910. sil call <sid>ArrangeCol(1, line('$'), 1)
  1911. endif
  1912. if s:csv_fold_headerline > 0
  1913. call <sid>NewRecord(s:csv_fold_headerline, s:csv_fold_headerline, 1)
  1914. exe 'sil '. (s:csv_fold_headerline+1).
  1915. \ 's/\s\+/\=repeat("-", strlen(submatch(0)))/g'
  1916. endif
  1917. if exists("b:csv_fixed_width_cols")
  1918. " There is no delimiter:
  1919. exe 'sil %s/'. pat. '/|/ge'
  1920. else
  1921. exe 'sil %s/'. b:delimiter. '/|/ge'
  1922. endif
  1923. " Add vertical bar in first column, if there isn't already one
  1924. sil %s/^[^|]/|&/e
  1925. " And add a final vertical bar, if there isn't already
  1926. sil %s/[^|]$/&|/e
  1927. let colwidth = strlen(substitute(getline('$'), '.', 'x', 'g'))
  1928. for line in [0, line('$')+1]
  1929. call append(line, repeat('-', colwidth))
  1930. endfor
  1931. syn clear
  1932. let &l:ma = _ma
  1933. call winrestview(_c)
  1934. endfu
  1935. fu! CSVPat(colnr, ...) "{{{3
  1936. " Make sure, we are working in a csv file
  1937. if &ft != 'csv'
  1938. return ''
  1939. endif
  1940. " encapsulates GetPat(), that returns the search pattern for a
  1941. " given column and tries to set the cursor at the specific position
  1942. let pat = <sid>GetPat(a:colnr, <SID>MaxColumns(), a:0 ? a:1 : '.*')
  1943. "let pos = match(pat, '.*\\ze') + 1
  1944. " Try to set the cursor at the beginning of the pattern
  1945. " does not work
  1946. "call setcmdpos(pos)
  1947. return pat
  1948. endfu
  1949. " Initialize Plugin "{{{2
  1950. call <SID>Init()
  1951. let &cpo = s:cpo_save
  1952. unlet s:cpo_save
  1953. " Vim Modeline " {{{2
  1954. " vim: set foldmethod=marker et: