summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Stankiewicz <sheerun@sher.pl>2013-09-12 17:30:48 +0200
committerAdam Stankiewicz <sheerun@sher.pl>2013-09-12 17:30:48 +0200
commit7d1b7e6f7123cd8b14f02221c7ec37d17d5d527e (patch)
tree9f0a5631c0dcaa8225f44004281f11265f694f14
parent5e3a8b92cf0fe8c46cccba1072d52ee486306dfd (diff)
downloadvim-polyglot-7d1b7e6f7123cd8b14f02221c7ec37d17d5d527e.tar.gz
vim-polyglot-7d1b7e6f7123cd8b14f02221c7ec37d17d5d527e.zip
Add csv syntax support
-rwxr-xr-xbuild.sh1
-rw-r--r--ftdetect/csv.vim3
-rw-r--r--ftplugin/csv.vim2446
-rw-r--r--syntax/csv.vim188
4 files changed, 2638 insertions, 0 deletions
diff --git a/build.sh b/build.sh
index 91e7022d..34594a8e 100755
--- a/build.sh
+++ b/build.sh
@@ -64,6 +64,7 @@ syntax 'vim-scripts/Puppet-Syntax-Highlighting' &
syntax 'beyondwords/vim-twig' &
syntax 'sudar/vim-arduino-syntax' &
syntax 'guns/vim-clojure-static' &
+syntax 'chrisbra/csv.vim' &
wait
diff --git a/ftdetect/csv.vim b/ftdetect/csv.vim
new file mode 100644
index 00000000..4a8b0ccc
--- /dev/null
+++ b/ftdetect/csv.vim
@@ -0,0 +1,3 @@
+" Install Filetype detection for CSV files
+au BufRead,BufNewFile *.csv,*.dat,*.tsv,*.tab set filetype=csv
+
diff --git a/ftplugin/csv.vim b/ftplugin/csv.vim
new file mode 100644
index 00000000..ddf01916
--- /dev/null
+++ b/ftplugin/csv.vim
@@ -0,0 +1,2446 @@
+" Filetype plugin for editing CSV files. "{{{1
+" Author: Christian Brabandt <cb@256bit.org>
+" Version: 0.29
+" Script: http://www.vim.org/scripts/script.php?script_id=2830
+" License: VIM License
+" Last Change: Wed, 14 Aug 2013 22:05:39 +0200
+" Documentation: see :help ft-csv.txt
+" GetLatestVimScripts: 2830 28 :AutoInstall: csv.vim
+"
+" Some ideas are taken from the wiki http://vim.wikia.com/wiki/VimTip667
+" though, implementation differs.
+
+" Plugin folklore "{{{2
+if v:version < 700 || exists('b:did_ftplugin')
+ finish
+endif
+let b:did_ftplugin = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" Function definitions: "{{{2
+"
+" Script specific functions "{{{2
+fu! <sid>Warn(mess) "{{{3
+ echohl WarningMsg
+ echomsg "CSV: " . a:mess
+ echohl Normal
+endfu
+
+fu! <sid>Init(startline, endline) "{{{3
+ " Hilight Group for Columns
+ if exists("g:csv_hiGroup")
+ let s:hiGroup = g:csv_hiGroup
+ else
+ let s:hiGroup="WildMenu"
+ endif
+ if !exists("g:csv_hiHeader")
+ let s:hiHeader = "Title"
+ else
+ let s:hiHeader = g:csv_hiHeader
+ endif
+ exe "hi link CSVHeaderLine" s:hiHeader
+
+ " Determine default Delimiter
+ if !exists("g:csv_delim")
+ let b:delimiter=<SID>GetDelimiter(a:startline, a:endline)
+ else
+ let b:delimiter=g:csv_delim
+ endif
+
+ " Define custom commentstring
+ if !exists("g:csv_comment")
+ let b:csv_cmt = split(&cms, '%s')
+ else
+ let b:csv_cmt = split(g:csv_comment, '%s')
+ endif
+
+ if empty(b:delimiter) && !exists("b:csv_fixed_width")
+ call <SID>Warn("No delimiter found. See :h csv-delimiter to set it manually!")
+ " Use a sane default as delimiter:
+ let b:delimiter = ','
+ endif
+
+ let s:del='\%(' . b:delimiter . '\|$\)'
+ let s:del_noend='\%(' . b:delimiter . '\)'
+ " Pattern for matching a single column
+ if !exists("g:csv_strict_columns") && !exists("g:csv_col")
+ \ && !exists("b:csv_fixed_width")
+ " - Allow double quotes as escaped quotes only insides double quotes
+ " - Allow linebreaks only, if g:csv_nl isn't set (this is
+ " only allowed in double quoted strings see RFC4180), though this
+ " does not work with :WhatColumn and might mess up syntax
+ " highlighting.
+ " - optionally allow whitespace in front of the fields (to make it
+ " work with :ArrangeCol (that is actually not RFC4180 valid))
+ " - Should work with most ugly solutions that are available
+ let b:col='\%(\%(\%(' . (b:delimiter !~ '\s' ? '\s*' : '') .
+ \ '"\%(' . (exists("g:csv_nl") ? '\_' : '' ) .
+ \ '[^"]\|""\)*"\)' . s:del . '\)\|\%(' .
+ \ '[^' . b:delimiter . ']*' . s:del . '\)\)'
+ let b:col_end='\%(\%(\%(' . (b:delimiter !~ '\s' ? '\s*' : '') .
+ \ '"\%(' . (exists("g:csv_nl") ? '\_' : '' ) .
+ \ '[^"]\|""\)*"\)' . s:del_noend . '\)\|\%(' .
+ \ '[^' . b:delimiter . ']*' . s:del_noend . '\)\)'
+ elseif !exists("g:csv_col") && exists("g:csv_strict_columns")
+ " strict columns
+ let b:col='\%([^' . b:delimiter . ']*' . s:del . '\)'
+ let b:col_end='\%([^' . b:delimiter . ']*' . s:del_noend . '\)'
+ elseif exists("b:csv_fixed_width")
+ " Fixed width column
+ let b:col=''
+ " Check for sane default
+ if b:csv_fixed_width =~? '[^0-9,]'
+ call <sid>Warn("Please specify the list of character columns" .
+ \ "like this: '1,3,5'. See also :h csv-fixedwidth")
+ return
+ endif
+ let b:csv_fixed_width_cols=split(b:csv_fixed_width, ',')
+ " Force evaluating as numbers
+ call map(b:csv_fixed_width_cols, 'v:val+0')
+ else
+ " User given column definition
+ let b:col = g:csv_col
+ let b:col_noend = g:csv_col
+ endif
+
+ " set filetype specific options
+ call <sid>LocalSettings('all')
+
+ " define buffer-local commands
+ call <SID>CommandDefinitions()
+
+ " Check Header line
+ " Defines which line is considered to be a header line
+ call <sid>CheckHeaderLine()
+
+ " CSV specific mappings
+ call <SID>CSVMappings()
+
+ " force reloading CSV Syntax Highlighting
+ if exists("b:current_syntax")
+ unlet b:current_syntax
+ " Force reloading syntax file
+ endif
+ call <sid>DoAutoCommands()
+ " enable CSV Menu
+ call <sid>Menu(1)
+ call <sid>DisableFolding()
+ silent do Syntax
+ unlet! b:csv_start b:csv_end
+
+ " Remove configuration variables
+ let b:undo_ftplugin .= "| unlet! b:delimiter b:col"
+ \ . "| unlet! b:csv_fixed_width_cols b:csv_filter"
+ \ . "| unlet! b:csv_fixed_width b:csv_list b:col_width"
+ \ . "| unlet! b:csv_SplitWindow b:csv_headerline"
+ \ . "| unlet! b:csv_thousands_sep b:csv_decimal_sep"
+ \. " | unlet! b:browsefilter b:csv_start b:csv_end"
+
+ " Delete all functions
+ " disabled currently, because otherwise when switching ft
+ " I think, all functions need to be read in again and this
+ " costs time.
+ "
+ " let b:undo_ftplugin .= "| delf <sid>Warn | delf <sid>Init |
+ " \ delf <sid>GetPat | delf <sid>SearchColumn | delf <sid>DelColumn |
+ " \ delf <sid>HiCol | delf <sid>GetDelimiter | delf <sid>WColumn |
+ " \ delf <sid>MaxColumns | delf <sid>ColWidth | delf <sid>ArCol |
+ " \ delf <sid>PrepUnArCol | delf <sid>UnArCol |
+ " \ delf <sid>CalculateColumnWidth | delf <sid>Columnize |
+ " \ delf <sid>GetColPat | delf <sid>SplitHeaderLine |
+ " \ delf <sid>SplitHeaderToggle | delf <sid>MoveCol |
+ " \ delf <sid>SortComplete | delf <sid>SortList | delf <sid>Sort |
+ " \ delf CSV_WCol | delf <sid>CopyCol | delf <sid>MoveColumn |
+ " \ delf <sid>SumColumn csv#EvalColumn | delf <sid>DoForEachColumn |
+ " \ delf <sid>PrepareDoForEachColumn | delf <sid>CSVMappings |
+ " \ delf <sid>Map | delf <sid>EscapeValue | delf <sid>FoldValue |
+ " \ delf <sid>PrepareFolding | delf <sid>OutputFilters |
+ " \ delf <sid>SortFilter | delf <sid>GetColumn |
+ " \ delf <sid>RemoveLastItem | delf <sid>DisableFolding |
+ " \ delf <sid>GetSID | delf <sid>CheckHeaderLine |
+ " \ delf <sid>AnalyzeColumn | delf <sid>Vertfold |
+ " \ delf <sid>InitCSVFixedWidth | delf <sid>LocalCmd |
+ " \ delf <sid>CommandDefinitions | delf <sid>NumberFormat |
+ " \ delf <sid>NewRecord | delf <sid>MoveOver | delf <sid>Menu |
+ " \ delf <sid>NewDelimiter | delf <sid>DuplicateRows | delf <sid>IN |
+ " \ delf <sid>SaveOptions | delf <sid>CheckDuplicates |
+ " \ delf <sid>CompleteColumnNr | delf <sid>CSVPat | delf <sid>Transpose |
+ " \ delf <sid>LocalSettings() | delf <sid>AddColumn | delf <sid>SubstituteInColumn
+endfu
+
+fu! <sid>LocalSettings(type) "{{{3
+ if a:type == 'all'
+ " CSV local settings
+ setl nostartofline tw=0 nowrap
+
+ " undo when setting a new filetype
+ let b:undo_ftplugin = "setlocal sol& tw< wrap<"
+
+ " Set browsefilter
+ if (v:version > 703 || (v:version == 703 && has("patch593")))
+ \ && exists("browsefilter")
+ let b:browsefilter="CSV Files (*.csv, *.dat)\t*.csv;*.dat\n".
+ \ "All Files\t*.*\n"
+ endif
+
+ if has("conceal")
+ setl cole=2 cocu=nc
+ let b:undo_ftplugin .= '| setl cole< cocu< '
+ endif
+
+ elseif a:type == 'fold'
+ let s:fdt = &l:fdt
+ let s:fcs = &l:fcs
+
+ if a:type == 'fold'
+ " Be sure to also fold away single screen lines
+ setl fen fdm=expr
+ setl fdl=0 fml=0 fdc=2
+ if !get(g:, 'csv_disable_fdt',0)
+ let &l:foldtext=strlen(v:folddashes) . ' lines hidden'
+ let &fcs=substitute(&fcs, 'fold:.,', '', '')
+ if !exists("b:csv_did_foldsettings")
+ let b:undo_ftplugin .= printf("|set fdt<|setl fcs=%s", escape(s:fcs, '\\| '))
+ endif
+ endif
+ if !exists("b:csv_did_foldsettings")
+ let b:undo_ftplugin .=
+ \ "| setl fen< fdm< fdl< fdc< fml< fde<"
+ let b:csv_did_foldsettings = 1
+ let b:undo_ftplugin .= "| unlet! b:csv_did_foldsettings"
+ endif
+ endif
+ endif
+endfu
+
+fu! <sid>DoAutoCommands() "{{{3
+ " Highlight column, on which the cursor is?
+ if exists("g:csv_highlight_column") && g:csv_highlight_column =~? 'y' &&
+ \ !exists("#CSV_HI#CursorMoved")
+ aug CSV_HI
+ au!
+ au CursorMoved <buffer> HiColumn
+ aug end
+ " Set highlighting for column, on which the cursor is currently
+ HiColumn
+ elseif exists("#CSV_HI#CursorMoved")
+ aug CSV_HI
+ au! CursorMoved <buffer>
+ aug end
+ aug! CSV_HI
+ " Remove any existing highlighting
+ HiColumn!
+ endif
+ " undo autocommand:
+ let b:undo_ftplugin .= '| exe "sil! au! CSV_HI CursorMoved <buffer> "'
+ let b:undo_ftplugin .= '| exe "sil! aug! CSV_HI" |exe "sil! HiColumn!"'
+
+ " Visually arrange columns when opening a csv file
+ if exists("g:csv_autocmd_arrange") &&
+ \ !exists("#CSV_Edit#BufReadPost")
+ aug CSV_Edit
+ au!
+ au BufReadPost,BufWritePost *.csv,*.dat :sil %ArrangeColumn
+ au BufWritePre *.csv,*.dat :sil %UnArrangeColumn
+ aug end
+ elseif exists("#CSV_Edit#BufReadPost")
+ aug CSV_Edit
+ au!
+ aug end
+ aug! CSV_Edit
+ endif
+ " undo autocommand:
+ let b:undo_ftplugin .= '| exe "sil! au! CSV_Edit "'
+ let b:undo_ftplugin .= '| exe "sil! aug! CSV_Edit"'
+
+" if !exists("#CSV_ColorScheme#ColorScheme")
+" " Make sure, syntax highlighting is applied
+" " after changing the colorscheme
+" augroup CSV_ColorScheme
+" au!
+" au ColorScheme *.csv,*.dat,*.tsv,*.tab do Syntax
+" augroup end
+" endif
+" let b:undo_ftplugin .= '| exe "sil! au! CSV_ColorScheme "'
+" let b:undo_ftplugin .= '| exe "sil! aug! CSV_ColorScheme"'
+
+ if has("gui_running") && !exists("#CSV_Menu#FileType")
+ augroup CSV_Menu
+ au!
+ au FileType * call <sid>Menu(&ft=='csv')
+ au BufEnter <buffer> call <sid>Menu(1) " enable
+ au BufLeave <buffer> call <sid>Menu(0) " disable
+ au BufNewFile,BufNew * call <sid>Menu(0)
+ augroup END
+ "let b:undo_ftplugin .= '| sil! amenu disable CSV'
+ let b:undo_ftplugin .= '| sil! call <sid>Menu(0)'
+ endif
+endfu
+
+fu! <sid>GetPat(colnr, maxcolnr, pat) "{{{3
+ if a:colnr > 1 && a:colnr < a:maxcolnr
+ if !exists("b:csv_fixed_width_cols")
+ return '^' . <SID>GetColPat(a:colnr-1,0) . '\%([^' .
+ \ b:delimiter . ']\{-}\)\?\zs' . a:pat . '\ze' .
+ \ '\%([^' . b:delimiter .']\{-}\)\?' .
+ \ b:delimiter . <SID>GetColPat(a:maxcolnr - a:colnr, 0) .
+ \ '$'
+ else
+ return '\%' . b:csv_fixed_width_cols[(a:colnr - 1)] . 'c\zs'
+ \ . a:pat . '.\{-}\ze\%'
+ \ . (b:csv_fixed_width_cols[a:colnr]) . 'c\ze'
+ endif
+ elseif a:colnr == a:maxcolnr
+ if !exists("b:csv_fixed_width_cols")
+ return '^' . <SID>GetColPat(a:colnr - 1,0) .
+ \ '\%([^' . b:delimiter .
+ \ ']\{-}\)\?\zs' . a:pat . '\ze'
+ else
+ return '\%' . b:csv_fixed_width_cols[-1] .
+ \ 'c\zs' . a:pat . '\ze'
+ endif
+ else " colnr = 1
+ if !exists("b:csv_fixed_width_cols")
+ return '^' . '\%([^' . b:delimiter . ']\{-}\)\?\zs' . a:pat .
+ \ '\ze\%([^' . b:delimiter . ']*\)\?' . b:delimiter .
+ \ <SID>GetColPat(a:maxcolnr -1 , 0) . '$'
+ else
+ return a:pat . '\ze.\{-}\%' . b:csv_fixed_width_cols[1] . 'c'
+ endif
+ endif
+ return ''
+endfu
+
+fu! <sid>SearchColumn(arg) "{{{3
+ try
+ let arglist=split(a:arg)
+ if len(arglist) == 1
+ let colnr=<SID>WColumn()
+ let pat=substitute(arglist[0], '^\(.\)\(.*\)\1$', '\2', '')
+ if pat == arglist[0]
+ throw "E684"
+ endif
+ else
+ " Determine whether the first word in the argument is a number
+ " (of the column to search).
+ let colnr = substitute( a:arg, '^\s*\(\d\+\)\s.*', '\1', '' )
+ " If it is _not_ a number,
+ if colnr == a:arg
+ " treat the whole argument as the pattern.
+ let pat = substitute(a:arg,
+ \ '^\s*\(\S\)\(.*\)\1\s*$', '\2', '' )
+ if pat == a:arg
+ throw "E684"
+ endif
+ let colnr = <SID>WColumn()
+ else
+ " if the first word tells us the number of the column,
+ " treat the rest of the argument as the pattern.
+ let pat = substitute(a:arg,
+ \ '^\s*\d\+\s*\(\S\)\(.*\)\1\s*$', '\2', '' )
+ if pat == a:arg
+ throw "E684"
+ endif
+ endif
+" let colnr=arglist[0]
+" let pat=substitute(arglist[1], '^\(.\)\(.*\)\1$', '\2', '')
+" if pat == arglist[1]
+" throw "E684"
+" endif
+ endif
+ "catch /^Vim\%((\a\+)\)\=:E684/
+ catch /E684/ " catch error index out of bounds
+ call <SID>Warn("Error! Usage :SearchInColumn [<colnr>] /pattern/")
+ return 1
+ endtry
+ let maxcolnr = <SID>MaxColumns()
+ if colnr > maxcolnr
+ call <SID>Warn("There exists no column " . colnr)
+ return 1
+ endif
+ let @/ = <sid>GetPat(colnr, maxcolnr, pat)
+ try
+ norm! n
+ catch /^Vim\%((\a\+)\)\=:E486/
+ " Pattern not found
+ echohl Error
+ echomsg "E486: Pattern not found in column " . colnr . ": " . pat
+ if &vbs > 0
+ echomsg substitute(v:exception, '^[^:]*:', '','')
+ endif
+ echohl Normal
+ endtry
+endfu
+
+
+fu! <sid>DeleteColumn(arg) "{{{3
+ let _wsv = winsaveview()
+ if a:arg =~ '^[/]'
+ let i = 0
+ let pat = a:arg[1:]
+ call cursor(1,1)
+ while search(pat, 'cW')
+ " Delete matching column
+ sil call <sid>DelColumn('')
+ let i+=1
+ endw
+ else
+ let i = 1
+ sil call <sid>DelColumn(a:arg)
+ endif
+ if i > 1
+ call <sid>Warn(printf("%d columns deleted", i))
+ else
+ call <sid>Warn("1 column deleted")
+ endif
+ call winrestview(_wsv)
+endfu
+
+fu! <sid>DelColumn(colnr) "{{{3
+ let maxcolnr = <SID>MaxColumns()
+ let _p = getpos('.')
+
+ if empty(a:colnr)
+ let colnr=<SID>WColumn()
+ else
+ let colnr=a:colnr
+ endif
+
+ if colnr > maxcolnr
+ call <SID>Warn("There exists no column " . colnr)
+ return
+ endif
+
+ if colnr != '1'
+ if !exists("b:csv_fixed_width_cols")
+ let pat= '^' . <SID>GetColPat(colnr-1,1) . b:col
+ else
+ let pat= <SID>GetColPat(colnr,0)
+ endif
+ else
+ " distinction between csv and fixed width does not matter here
+ let pat= '^' . <SID>GetColPat(colnr,0)
+ endif
+ if &ro
+ let ro = 1
+ setl noro
+ else
+ let ro = 0
+ endif
+ exe ':%s/' . escape(pat, '/') . '//'
+ call setpos('.', _p)
+ if ro
+ setl ro
+ endif
+endfu
+
+fu! <sid>HiCol(colnr, bang) "{{{3
+ if a:colnr > <SID>MaxColumns() && !a:bang
+ call <SID>Warn("There exists no column " . a:colnr)
+ return
+ endif
+ if !a:bang
+ if empty(a:colnr)
+ let colnr=<SID>WColumn()
+ else
+ let colnr=a:colnr
+ endif
+
+ if colnr==1
+ let pat='^'. <SID>GetColPat(colnr,0)
+ elseif !exists("b:csv_fixed_width_cols")
+ let pat='^'. <SID>GetColPat(colnr-1,1) . b:col
+ else
+ let pat=<SID>GetColPat(colnr,0)
+ endif
+ endif
+
+ if exists("*matchadd")
+ if exists("s:matchid")
+ " ignore errors, that come from already deleted matches
+ sil! call matchdelete(s:matchid)
+ endif
+ " Additionally, filter all matches, that could have been used earlier
+ let matchlist=getmatches()
+ call filter(matchlist, 'v:val["group"] !~ s:hiGroup')
+ call setmatches(matchlist)
+ if a:bang
+ return
+ endif
+ let s:matchid=matchadd(s:hiGroup, pat, 0)
+ elseif !a:bang
+ exe ":2match " . s:hiGroup . ' /' . pat . '/'
+ endif
+endfu
+
+fu! <sid>GetDelimiter(first, last) "{{{3
+ if !exists("b:csv_fixed_width_cols")
+ let _cur = getpos('.')
+ let _s = @/
+ let Delim= {0: ';', 1: ',', 2: '|', 3: ' '}
+ let temp = {}
+ " :silent :s does not work with lazyredraw
+ let _lz = &lz
+ set nolz
+ for i in values(Delim)
+ redir => temp[i]
+ exe "silent! ". a:first. ",". a:last. "s/" . i . "/&/nge"
+ redir END
+ endfor
+ let &lz = _lz
+ let Delim = map(temp, 'matchstr(substitute(v:val, "\n", "", ""), "^\\d\\+")')
+ let Delim = filter(temp, 'v:val=~''\d''')
+ let max = max(values(temp))
+
+ let result=[]
+ call setpos('.', _cur)
+ let @/ = _s
+ for [key, value] in items(Delim)
+ if value == max
+ return key
+ endif
+ endfor
+ return ''
+ else
+ " There is no delimiter for fixedwidth files
+ return ''
+ endif
+endfu
+
+fu! <sid>WColumn(...) "{{{3
+ " Return on which column the cursor is
+ let _cur = getpos('.')
+ if !exists("b:csv_fixed_width_cols")
+ let line=getline('.')
+ " move cursor to end of field
+ "call search(b:col, 'ec', line('.'))
+ call search(b:col, 'ec')
+ let end=col('.')-1
+ let fields=(split(line[0:end],b:col.'\zs'))
+ let ret=len(fields)
+ if exists("a:1") && a:1 > 0
+ " bang attribute
+ let head = split(getline(1),b:col.'\zs')
+ " remove preceeding whitespace
+ let ret = substitute(head[ret-1], '^\s\+', '', '')
+ " remove delimiter
+ let ret = substitute(ret, b:delimiter. '$', '', '')
+ endif
+ else
+ let temp=getpos('.')[2]
+ let j=1
+ let ret = 1
+ for i in sort(b:csv_fixed_width_cols, "<sid>SortList")
+ if temp >= i
+ let ret = j
+ endif
+ let j += 1
+ endfor
+ endif
+ call setpos('.',_cur)
+ return ret
+endfu
+
+fu! <sid>MaxColumns(...) "{{{3
+ if exists("a:0") && a:0 == 1
+ let this_col = 1
+ else
+ let this_col = 0
+ endif
+ "return maximum number of columns in first 10 lines
+ if !exists("b:csv_fixed_width_cols")
+ if this_col
+ let i = a:1
+ else
+ let i = 1
+ endif
+ while 1
+ let l = getline(i, i+10)
+
+ " Filter comments out
+ let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ call filter(l, 'v:val !~ pat')
+ if !empty(l) || this_col
+ break
+ else
+ let i+=10
+ endif
+ endw
+
+ if empty(l)
+ throw 'csv:no_col'
+ endif
+ let fields=[]
+ let result=0
+ for item in l
+ let temp=len(split(item, b:col.'\zs'))
+ let result=(temp>result ? temp : result)
+ endfor
+ return result
+ else
+ return len(b:csv_fixed_width_cols)
+ endif
+endfu
+
+fu! <sid>ColWidth(colnr) "{{{3
+ " Return the width of a column
+ " Internal function
+ let width=20 "Fallback (wild guess)
+ let tlist=[]
+
+ if !exists("b:csv_fixed_width_cols")
+ if !exists("b:csv_list")
+ let b:csv_list=getline(1,'$')
+ let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ call filter(b:csv_list, 'v:val !~ pat')
+ call filter(b:csv_list, '!empty(v:val)')
+ call map(b:csv_list, 'split(v:val, b:col.''\zs'')')
+ endif
+ try
+ for item in b:csv_list
+ call add(tlist, item[a:colnr-1])
+ endfor
+ " we have a list of the first 10 rows
+ " Now transform it to a list of field a:colnr
+ " and then return the maximum strlen
+ " That could be done in 1 line, but that would look ugly
+ "call map(list, 'split(v:val, b:col."\\zs")[a:colnr-1]')
+ call map(tlist, 'substitute(v:val, ".", "x", "g")')
+ call map(tlist, 'strlen(v:val)')
+ return max(tlist)
+ catch
+ throw "ColWidth-error"
+ return width
+ endtry
+ else
+ let cols = len(b:csv_fixed_width_cols)
+ if a:colnr == cols
+ return strlen(substitute(getline('$'), '.', 'x', 'g')) -
+ \ b:csv_fixed_width_cols[cols-1] + 1
+ elseif a:colnr < cols && a:colnr > 0
+ return b:csv_fixed_width_cols[a:colnr] -
+ \ b:csv_fixed_width_cols[(a:colnr - 1)]
+ else
+ throw "ColWidth-error"
+ return 0
+ endif
+ endif
+endfu
+
+fu! <sid>ArrangeCol(first, last, bang) range "{{{3
+ "TODO: Why doesn't that work?
+ " is this because of the range flag?
+ " It's because of the way, Vim works with
+ " a:firstline and a:lastline parameter, therefore
+ " explicitly give the range as argument to the function
+ if exists("b:csv_fixed_width_cols")
+ " Nothing to do
+ call <sid>Warn("ArrangeColumn does not work with fixed width column!")
+ return
+ endif
+ let cur=winsaveview()
+ if a:bang || !exists("b:col_width")
+ if a:bang
+ " Force recalculating the Column width
+ unlet! b:csv_list
+ endif
+ " Force recalculation of Column width
+ call <sid>CalculateColumnWidth()
+ endif
+
+ if &ro
+ " Just in case, to prevent the Warning
+ " Warning: W10: Changing read-only file
+ let ro = 1
+ setl noro
+ else
+ let ro = 0
+ endif
+ exe a:first . ',' . a:last .'s/' . (b:col) .
+ \ '/\=<SID>Columnize(submatch(0))/' . (&gd ? '' : 'g')
+ " Clean up variables, that were only needed for <sid>Columnize() function
+ unlet! s:columnize_count s:max_cols s:prev_line
+ if ro
+ setl ro
+ unlet ro
+ endif
+ call winrestview(cur)
+endfu
+
+fu! <sid>PrepUnArrangeCol(first, last) "{{{3
+ " Because of the way, Vim works with
+ " a:firstline and a:lastline parameter,
+ " explicitly give the range as argument to the function
+ if exists("b:csv_fixed_width_cols")
+ " Nothing to do
+ call <sid>Warn("UnArrangeColumn does not work with fixed width column!")
+ return
+ endif
+ let cur=winsaveview()
+
+ if &ro
+ " Just in case, to prevent the Warning
+ " Warning: W10: Changing read-only file
+ setl noro
+ endif
+ exe a:first . ',' . a:last .'s/' . (b:col) .
+ \ '/\=<SID>UnArrangeCol(submatch(0))/' . (&gd ? '' : 'g')
+ " Clean up variables, that were only needed for <sid>Columnize() function
+ call winrestview(cur)
+endfu
+
+fu! <sid>UnArrangeCol(match) "{{{3
+ " Strip leading white space, also trims empty records:
+ return substitute(a:match, '^\s\+', '', '')
+ " only strip leading white space, if a non-white space follows:
+ "return substitute(a:match, '^\s\+\ze\S', '', '')
+endfu
+
+fu! <sid>CalculateColumnWidth() "{{{3
+ " Internal function, not called from external,
+ " does not work with fixed width columns
+ let b:col_width=[]
+ try
+ let s:max_cols=<SID>MaxColumns(line('.'))
+ for i in range(1,s:max_cols)
+ call add(b:col_width, <SID>ColWidth(i))
+ endfor
+ catch /csv:no_col/
+ call <sid>Warn("Error: getting Column numbers, aborting!")
+ catch /ColWidth/
+ call <sid>Warn("Error: getting Column Width, using default!")
+ endtry
+ " delete buffer content in variable b:csv_list,
+ " this was only necessary for calculating the max width
+ unlet! b:csv_list
+endfu
+
+fu! <sid>Columnize(field) "{{{3
+ " Internal function, not called from external,
+ " does not work with fixed width columns
+ if !exists("s:columnize_count")
+ let s:columnize_count = 0
+ endif
+
+ if !exists("s:max_cols")
+ let s:max_cols = len(b:col_width)
+ endif
+
+ if exists("s:prev_line") && s:prev_line != line('.')
+ let s:columnize_count = 0
+ endif
+
+ let s:prev_line = line('.')
+ " convert zero based indexed list to 1 based indexed list,
+ " Default: 20 width, in case that column width isn't defined
+ " Careful: Keep this fast! Using
+ " let width=get(b:col_width,<SID>WColumn()-1,20)
+ " is too slow, so we are using:
+ let width=get(b:col_width, (s:columnize_count % s:max_cols), 20)
+
+ let s:columnize_count += 1
+ if v:version > 703 || v:version == 703 && has("patch713")
+ " printf knows about %S (e.g. can handle char length
+ return printf("%*S", width+1 , a:field)
+ else
+ " printf only handles bytes
+ if !exists("g:csv_no_multibyte") &&
+ \ match(a:field, '[^ -~]') != -1
+ " match characters outside the ascii range
+ let a = split(a:field, '\zs')
+ let add = eval(join(map(a, 'len(v:val)'), '+'))
+ let add -= len(a)
+ else
+ let add = 0
+ endif
+
+ " Add one for the frame
+ " plus additional width for multibyte chars,
+ " since printf(%*s..) uses byte width!
+ let width = width + add + 1
+
+ if width == strlen(a:field)
+ " Column has correct length, don't use printf()
+ return a:field
+ else
+ return printf("%*s", width , a:field)
+ endif
+ endif
+endfun
+
+fu! <sid>GetColPat(colnr, zs_flag) "{{{3
+ " Return Pattern for given column
+ if a:colnr > 1
+ if !exists("b:csv_fixed_width_cols")
+ let pat=b:col . '\{' . (a:colnr) . '\}'
+ else
+ if a:colnr >= len(b:csv_fixed_width_cols)
+ " Get last column
+ let pat='\%' . b:csv_fixed_width_cols[-1] . 'c.*'
+ else
+ let pat='\%' . b:csv_fixed_width_cols[(a:colnr - 1)] .
+ \ 'c.\{-}\%' . b:csv_fixed_width_cols[a:colnr] . 'c'
+ endif
+ endif
+ elseif !exists("b:csv_fixed_width_cols")
+ let pat=b:col
+ else
+ let pat='\%' . b:csv_fixed_width_cols[0] . 'c.\{-}' .
+ \ (len(b:csv_fixed_width_cols) > 1 ?
+ \ '\%' . b:csv_fixed_width_cols[1] . 'c' :
+ \ '')
+ endif
+ return pat . (a:zs_flag ? '\zs' : '')
+endfu
+
+fu! <sid>SetupQuitPre() "{{{3
+ " Setup QuitPre autocommand to quit cleanly
+ if exists("##QuitPre")
+ let bufnr=bufnr('')
+ noa wincmd p
+ augroup CSV_QuitPre
+ au!
+ exe "au QuitPre * ". bufnr. "bw"
+ augroup end
+ noa wincmd p
+ endif
+endfu
+
+fu! <sid>SplitHeaderLine(lines, bang, hor) "{{{3
+ if exists("b:csv_fixed_width_cols")
+ call <sid>Warn("Header does not work with fixed width column!")
+ return
+ endif
+ " Check that there exists a header line
+ call <sid>CheckHeaderLine()
+ if !a:bang
+ " A Split Header Window already exists,
+ " first close the already existing Window
+ if exists("b:csv_SplitWindow")
+ call <sid>SplitHeaderLine(a:lines, 1, a:hor)
+ endif
+ " Split Window
+ let _stl = &l:stl
+ let _sbo = &sbo
+ let a = []
+ let b=b:col
+ if a:hor
+ setl scrollopt=hor scrollbind
+ let _fdc = &l:fdc
+ let lines = empty(a:lines) ? s:csv_fold_headerline : a:lines
+ let a = getline(1,lines)
+ " Does it make sense to use the preview window?
+ " sil! pedit %
+ above sp +enew
+ call setline(1, a)
+ " Needed for syntax highlighting
+ "let b:col=b
+ "setl syntax=csv
+ sil! doautocmd FileType csv
+ 1
+ exe "resize" . lines
+ setl scrollopt=hor winfixheight nowrap
+ "let &l:stl=repeat(' ', winwidth(0))
+ let &l:stl="%#Normal#".repeat(' ',winwidth(0))
+ " set the foldcolumn to the same of the other window
+ let &l:fdc = _fdc
+ else
+ setl scrollopt=ver scrollbind
+ 0
+ let a=<sid>CopyCol('',1)
+ " Force recalculating columns width
+ unlet! b:csv_list
+ try
+ let width = <sid>ColWidth(1)
+ catch /ColWidth/
+ call <sid>Warn("Error: getting Column Width, using default!")
+ endtry
+ " Does it make sense to use the preview window?
+ "vert sil! pedit |wincmd w | enew!
+ above vsp +enew
+ call append(0, a)
+ $d _
+ sil %s/.*/\=printf("%.*s", width, submatch(0))/eg
+ 0
+ exe "vert res" width
+ let b:col=b
+ call matchadd("CSVHeaderLine", b:col)
+ setl scrollopt=ver winfixwidth
+ endif
+ call <sid>SetupQuitPre()
+ let win = winnr()
+ setl scrollbind buftype=nowrite bufhidden=wipe noswapfile nobuflisted
+ wincmd p
+ let b:csv_SplitWindow = win
+ aug CSV_Preview
+ au!
+ au BufWinLeave <buffer> call <sid>SplitHeaderLine(0, 1, 0)
+ aug END
+ else
+ " Close split window
+ if !exists("b:csv_SplitWindow")
+ return
+ endif
+ exe b:csv_SplitWindow . "wincmd w"
+ if exists("_stl")
+ let &l:stl = _stl
+ endif
+ if exists("_sbo")
+ let &sbo = _sbo
+ endif
+ setl noscrollbind
+ wincmd c
+ "pclose!
+ unlet! b:csv_SplitWindow
+ aug CSV_Preview
+ au!
+ aug END
+ aug! CSV_Preview
+ endif
+endfu
+
+fu! <sid>SplitHeaderToggle(hor) "{{{3
+ if !exists("b:csv_SplitWindow")
+ :call <sid>SplitHeaderLine(1,0,a:hor)
+ else
+ :call <sid>SplitHeaderLine(1,1,a:hor)
+ endif
+endfu
+
+" TODO: from here on add logic for fixed-width csv files!
+fu! <sid>MoveCol(forward, line) "{{{3
+ " Move cursor position upwards/downwards left/right
+ let colnr=<SID>WColumn()
+ let maxcol=<SID>MaxColumns()
+ let cpos=getpos('.')[2]
+ if !exists("b:csv_fixed_width_cols")
+ call search(b:col, 'bc', line('.'))
+ endif
+ let spos=getpos('.')[2]
+
+ " Check for valid column
+ " a:forward == 1 : search next col
+ " a:forward == -1: search prev col
+ " a:forward == 0 : stay in col
+ if colnr - v:count1 >= 1 && a:forward == -1
+ let colnr -= v:count1
+ elseif colnr - v:count1 < 1 && a:forward == -1
+ let colnr = 0
+ elseif colnr + v:count1 <= maxcol && a:forward == 1
+ let colnr += v:count1
+ elseif colnr + v:count1 > maxcol && a:forward == 1
+ let colnr = maxcol + 1
+ endif
+
+ let line=a:line
+ if line < 1
+ let line=1
+ elseif line > line('$')
+ let line=line('$')
+ endif
+
+ " Generate search pattern
+ if colnr == 1
+ let pat = '^' . <SID>GetColPat(colnr-1,0)
+ "let pat = pat . '\%' . line . 'l'
+ elseif (colnr == 0) || (colnr == maxcol + 1)
+ if !exists("b:csv_fixed_width_cols")
+ let pat=b:col
+ else
+ if a:forward > 0
+ " Move forwards
+ let pat=<sid>GetColPat(1, 0)
+ else
+ " Move backwards
+ let pat=<sid>GetColPat(maxcol, 0)
+ endif
+ endif
+ else
+ if !exists("b:csv_fixed_width_cols")
+ let pat='^'. <SID>GetColPat(colnr-1,1) . b:col
+ else
+ let pat=<SID>GetColPat(colnr,0)
+ endif
+ "let pat = pat . '\%' . line . 'l'
+ endif
+
+ " Search
+ " move left/right
+ if a:forward > 0
+ call search(pat, 'W')
+ elseif a:forward < 0
+ if colnr > 0 || cpos == spos
+ call search('.\ze'.pat, 'bWe')
+ while getpos('.')[2] == cpos
+ " cursor didn't move, move cursor one cell to the left
+ norm! h
+ if colnr > 0
+ call <sid>MoveCol(-1, line('.'))
+ else
+ norm! 0
+ endif
+ endw
+ else
+ norm! 0
+ endif
+ " Moving upwards/downwards
+ elseif line >= line('.')
+ call search(pat . '\%' . line . 'l', '', line)
+ " Move to the correct screen column
+ " This is a best effort approach, we might still
+ " leave the column (if the next column is shorter)
+ if !exists("b:csv_fixed_width_cols")
+ let a = getpos('.')
+ let a[2]+= cpos-spos
+ else
+ let a = getpos('.')
+ let a[2] = cpos
+ endif
+ call setpos('.', a)
+ elseif line < line('.')
+ call search(pat . '\%' . line . 'l', 'b', line)
+ " Move to the correct screen column
+ if !exists("b:csv_fixed_width_cols")
+ let a = getpos('.')
+ let a[2]+= cpos-spos
+ else
+ let a = getpos('.')
+ let a[2] = cpos
+ endif
+ call setpos('.', a)
+ endif
+endfun
+
+fu! <sid>SortComplete(A,L,P) "{{{3
+ return join(range(1,<sid>MaxColumns()),"\n")
+endfun
+
+fu! <sid>SortList(a1, a2) "{{{3
+ return a:a1+0 == a:a2+0 ? 0 : a:a1+0 > a:a2+0 ? 1 : -1
+endfu
+
+fu! <sid>Sort(bang, line1, line2, colnr) range "{{{3
+ let wsv=winsaveview()
+ if a:colnr =~? 'n'
+ let numeric = 1
+ else
+ let numeric = 0
+ endif
+ let col = (empty(a:colnr) || a:colnr !~? '\d\+') ? <sid>WColumn() : a:colnr+0
+ if col != 1
+ if !exists("b:csv_fixed_width_cols")
+ let pat= '^' . <SID>GetColPat(col-1,1) . b:col
+ else
+ let pat= '^' . <SID>GetColPat(col,0)
+ endif
+ else
+ let pat= '^' . <SID>GetColPat(col,0)
+ endif
+ exe a:line1 ',' a:line2 . "sort" . (a:bang ? '!' : '') .
+ \' r ' . (numeric ? 'n' : '') . ' /' . pat . '/'
+ call winrestview(wsv)
+endfun
+
+fu! <sid>CopyCol(reg, col) "{{{3
+ " Return Specified Column into register reg
+ let col = a:col == "0" ? <sid>WColumn() : a:col+0
+ let mcol = <sid>MaxColumns()
+ if col == '$' || col > mcol
+ let col = mcol
+ endif
+ let a = []
+ " Don't get lines, that are currently filtered away
+ if !exists("b:csv_filter") || empty(b:csv_filter)
+ let a=getline(1, '$')
+ else
+ for line in range(1, line('$'))
+ if foldlevel(line)
+ continue
+ else
+ call add(a, getline(line))
+ endif
+ endfor
+ endif
+ " Filter comments out
+ let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ call filter(a, 'v:val !~ pat')
+
+ if !exists("b:csv_fixed_width_cols")
+ call map(a, 'split(v:val, ''^'' . b:col . ''\zs'')[col-1]')
+ else
+ call map(a, 'matchstr(v:val, <sid>GetColPat(col, 0))')
+ endif
+ if a:reg =~ '[-"0-9a-zA-Z*+]'
+ "exe ':let @' . a:reg . ' = "' . join(a, "\n") . '"'
+ " set the register to blockwise mode
+ call setreg(a:reg, join(a, "\n"), 'b')
+ else
+ return a
+ endif
+endfu
+
+fu! <sid>MoveColumn(start, stop, ...) range "{{{3
+ " Move column behind dest
+ " Explicitly give the range as argument,
+ " cause otherwise, Vim would move the cursor
+ let wsv = winsaveview()
+
+ let col = <sid>WColumn()
+ let max = <sid>MaxColumns()
+
+ " If no argument is given, move current column after last column
+ let source=(exists("a:1") && a:1 > 0 && a:1 <= max ? a:1 : col)
+ let dest =(exists("a:2") && a:2 > 0 && a:2 <= max ? a:2 : max)
+
+ " translate 1 based columns into zero based list index
+ let source -= 1
+ let dest -= 1
+
+ if source >= dest
+ call <sid>Warn("Destination column before source column, aborting!")
+ return
+ endif
+
+ " Swap line by line, instead of reading the whole range into memory
+
+ for i in range(a:start, a:stop)
+ let content = getline(i)
+ if content =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ " skip comments
+ continue
+ endif
+ if !exists("b:csv_fixed_width_cols")
+ let fields=split(content, b:col . '\zs')
+ " Add delimiter to destination column, in case there was none,
+ " remove delimiter from source, in case destination did not have one
+ if matchstr(fields[dest], '.$') !~? b:delimiter
+ let fields[dest] = fields[dest] . b:delimiter
+ if matchstr(fields[source], '.$') =~? b:delimiter
+ let fields[source] = substitute(fields[source],
+ \ '^\(.*\).$', '\1', '')
+ endif
+ endif
+ else
+ let fields=[]
+ " this is very inefficient!
+ for j in range(1, max, 1)
+ call add(fields, matchstr(content, <sid>GetColPat(j,0)))
+ endfor
+ endif
+
+ let fields= (source == 0 ? [] : fields[0 : (source-1)])
+ \ + fields[ (source+1) : dest ]
+ \ + [ fields[source] ] + fields[(dest+1):]
+
+ call setline(i, join(fields, ''))
+ endfor
+
+ call winrestview(wsv)
+
+endfu
+
+fu! <sid>AddColumn(start, stop, ...) range "{{{3
+ " Add new empty column
+ " Explicitly give the range as argument,
+ " cause otherwise, Vim would move the cursor
+ if exists("b:csv_fixed_width_cols")
+ call <sid>Warn("Adding Columns only works for delimited files")
+ return
+ endif
+
+ let wsv = winsaveview()
+
+ let col = <sid>WColumn()
+ let max = <sid>MaxColumns()
+
+ " If no argument is given, add column after current column
+ if exists("a:1")
+ if a:1 == '$' || a:1 >= max
+ let pos = max
+ elseif a:1 <= 0
+ let pos = col
+ endif
+ else
+ let pos = col
+ endif
+ let cnt=(exists("a:2") && a:2 > 0 ? a:2 : 1)
+
+ " translate 1 based columns into zero based list index
+ let pos -= 1
+ let col -= 1
+
+ if pos == 0
+ let pat = '^'
+ elseif pos == max-1
+ let pat = '$'
+ else
+ let pat = <sid>GetColPat(pos,1)
+ endif
+
+ if pat != '$' || (pat == '$' && getline(a:stop)[-1:] == b:delimiter)
+ let subst = repeat(' '. b:delimiter, cnt)
+ else
+ let subst = repeat(b:delimiter. ' ', cnt)
+ endif
+
+ " if the data contains comments, substitute one line after another
+ " skipping comment lines (we could do it with a single :s statement,
+ " but that would fail for the first and last column.
+
+ let commentpat = '\%(\%>'.(a:start-1).'l\V'.
+ \ escape(b:csv_cmt[0], '\\').'\m\)'. '\&\%(\%<'.
+ \ (a:stop+1). 'l\V'. escape(b:csv_cmt[0], '\\'). '\m\)'
+ if search(commentpat)
+ for i in range(a:start, a:stop)
+ let content = getline(i)
+ if content =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ " skip comments
+ continue
+ endif
+ exe printf("sil %ds/%s/%s/e", i, pat, subst)
+ endfor
+ else
+ " comments should by default be skipped (pattern shouldn't match)
+ exe printf("sil %d,%ds/%s/%s/e", a:start, a:stop, pat, subst)
+ endif
+
+ call winrestview(wsv)
+endfu
+
+fu! <sid>SumColumn(list) "{{{3
+ " Sum a list of values, but only consider the digits within each value
+ " parses the digits according to the given format (if none has been
+ " specified, assume POSIX format (without thousand separator) If Vim has
+ " does not support floats, simply sum up only the integer part
+ if empty(a:list)
+ return 0
+ else
+ let sum = has("float") ? 0.0 : 0
+ for item in a:list
+ if empty(item)
+ continue
+ endif
+ let nr = matchstr(item, '\d\(.*\d\)\?$')
+ let format1 = '^\d\+\zs\V' . s:nr_format[0] . '\m\ze\d'
+ let format2 = '\d\+\zs\V' . s:nr_format[1] . '\m\ze\d'
+ try
+ let nr = substitute(nr, format1, '', '')
+ if has("float") && s:nr_format[1] != '.'
+ let nr = substitute(nr, format2, '.', '')
+ endif
+ catch
+ let nr = 0
+ endtry
+ let sum += (has("float") ? str2float(nr) : (nr + 0))
+ endfor
+ if has("float")
+ if float2nr(sum) == sum
+ return float2nr(sum)
+ else
+ return printf("%.2f", sum)
+ endif
+ endif
+ return sum
+ endif
+endfu
+
+fu! <sid>DoForEachColumn(start, stop, bang) range "{{{3
+ " Do something for each column,
+ " e.g. generate SQL-Statements, convert to HTML,
+ " something like this
+ " TODO: Define the function
+ " needs a csv_pre_convert variable
+ " csv_post_convert variable
+ " csv_convert variable
+ " result contains converted buffer content
+ let result = []
+
+ if !exists("g:csv_convert")
+ call <sid>Warn("You need to define how to convert your data using" .
+ \ "the g:csv_convert variable, see :h csv-convert")
+ return
+ endif
+
+ if exists("g:csv_pre_convert") && !empty(g:csv_pre_convert)
+ call add(result, g:csv_pre_convert)
+ endif
+
+ for item in range(a:start, a:stop, 1)
+ let t = g:csv_convert
+ let line = getline(item)
+ if line =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ " Filter comments out
+ call add(result, line)
+ continue
+ endif
+ let context = split(g:csv_convert, '%s')
+ let columns = len(context)
+ if columns > <sid>MaxColumns()
+ let columns = <sid>MaxColumns()
+ elseif columns == 1
+ call <sid>Warn("No Columns defined in your g:csv_convert variable, Aborting")
+ return
+ endif
+
+ if !exists("b:csv_fixed_width_cols")
+ let fields=split(line, b:col . '\zs')
+ if a:bang
+ call map(fields, 'substitute(v:val, b:delimiter .
+ \ ''\?$'' , "", "")')
+ endif
+ else
+ let fields=[]
+ for j in range(1, columns, 1)
+ call add(fields, matchstr(line, <sid>GetColPat(j,0)))
+ endfor
+ endif
+ for j in range(1, columns, 1)
+ let t=substitute(t, '%s', fields[j-1], '')
+ endfor
+ call add(result, t)
+ endfor
+
+ if exists("g:csv_post_convert") && !empty(g:csv_post_convert)
+ call add(result, g:csv_post_convert)
+ endif
+
+ new
+ call append('$', result)
+ 1d _
+
+endfun
+
+fu! <sid>PrepareDoForEachColumn(start, stop, bang) range"{{{3
+ let pre = exists("g:csv_pre_convert") ? g:csv_pre_convert : ''
+ let g:csv_pre_convert=input('Pre convert text: ', pre)
+ let post = exists("g:csv_post_convert") ? g:csv_post_convert : ''
+ let g:csv_post_convert=input('Post convert text: ', post)
+ let convert = exists("g:csv_convert") ? g:csv_convert : ''
+ let g:csv_convert=input("Converted text, use %s for column input:\n", convert)
+ call <sid>DoForEachColumn(a:start, a:stop, a:bang)
+endfun
+fu! <sid>EscapeValue(val) "{{{3
+ return '\V' . escape(a:val, '\')
+endfu
+
+fu! <sid>FoldValue(lnum, filter) "{{{3
+ call <sid>CheckHeaderLine()
+
+ if (a:lnum == s:csv_fold_headerline)
+ " Don't fold away the header line
+ return 0
+ endif
+ let result = 0
+
+ for item in values(a:filter)
+ " always fold comments away
+ let content = getline(a:lnum)
+ if content =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ return 1
+ elseif eval('content' . (item.match ? '!~' : '=~') . 'item.pat')
+ let result += 1
+ endif
+ endfor
+ return (result > 0)
+endfu
+
+fu! <sid>PrepareFolding(add, match) "{{{3
+ if !has("folding")
+ return
+ endif
+
+ " Move folded-parts away?
+ if exists("g:csv_move_folds")
+ let s:csv_move_folds = g:csv_move_folds
+ else
+ let s:csv_move_folds = 0
+ endif
+
+ if !exists("b:csv_filter")
+ let b:csv_filter = {}
+ endif
+ if !exists("s:filter_count") || s:filter_count < 1
+ let s:filter_count = 0
+ endif
+ let cpos = winsaveview()
+
+ if !a:add
+ " remove last added item from filter
+ if !empty(b:csv_filter)
+ call <sid>RemoveLastItem(s:filter_count)
+ let s:filter_count -= 1
+ if empty(b:csv_filter)
+ call <sid>DisableFolding()
+ return
+ endif
+ else
+ " Disable folding, if no pattern available
+ call <sid>DisableFolding()
+ return
+ endif
+ else
+
+ let col = <sid>WColumn()
+ let max = <sid>MaxColumns()
+ let a = <sid>GetColumn(line('.'), col)
+
+ if !exists("b:csv_fixed_width")
+ try
+ " strip leading whitespace
+ if (a =~ '\s\+'. b:delimiter . '$')
+ let b = split(a, '^\s\+\ze[^' . b:delimiter. ']\+')[0]
+ else
+ let b = a
+ endif
+ catch /^Vim\%((\a\+)\)\=:E684/
+ " empty pattern - should match only empty columns
+ let b = a
+ endtry
+
+ " strip trailing delimiter
+ try
+ let a = split(b, b:delimiter . '$')[0]
+ catch /^Vim\%((\a\+)\)\=:E684/
+ let a = b
+ endtry
+
+ if a == b:delimiter
+ try
+ let a=repeat(' ', <sid>ColWidth(col))
+ catch
+ " no-op
+ endtry
+ endif
+ endif
+
+ " Make a column pattern
+ let b= '\%(' .
+ \ (exists("b:csv_fixed_width") ? '.*' : '') .
+ \ <sid>GetPat(col, max, <sid>EscapeValue(a) . '\m') .
+ \ '\)'
+
+ let s:filter_count += 1
+ let b:csv_filter[s:filter_count] = { 'pat': b, 'id': s:filter_count,
+ \ 'col': col, 'orig': a, 'match': a:match}
+
+ endif
+ " Put the pattern into the search register, so they will also
+ " be highlighted
+" let @/ = ''
+" for val in sort(values(b:csv_filter), '<sid>SortFilter')
+" let @/ .= val.pat . (val.id == s:filter_count ? '' : '\&')
+" endfor
+ let sid = <sid>GetSID()
+
+ " Fold settings:
+ call <sid>LocalSettings('fold')
+ " Don't put spaces between the arguments!
+ exe 'setl foldexpr=<snr>' . sid . '_FoldValue(v:lnum,b:csv_filter)'
+
+ " Move folded area to the bottom, so there is only on consecutive
+ " non-folded area
+ if exists("s:csv_move_folds") && s:csv_move_folds
+ \ && !&l:ro && &l:ma
+ folddoclosed m$
+ let cpos.lnum = s:csv_fold_headerline + 1
+ endif
+ call winrestview(cpos)
+endfu
+
+fu! <sid>OutputFilters(bang) "{{{3
+ if !a:bang
+ call <sid>CheckHeaderLine()
+ if s:csv_fold_headerline
+ let title="Nr\tMatch\tCol\t Name\tValue"
+ else
+ let title="Nr\tMatch\tCol\tValue"
+ endif
+ echohl "Title"
+ echo printf("%s", title)
+ echo printf("%s", repeat("=",strdisplaywidth(title)))
+ echohl "Normal"
+ if !exists("b:csv_filter") || empty(b:csv_filter)
+ echo printf("%s", "No active filter")
+ else
+ let items = values(b:csv_filter)
+ call sort(items, "<sid>SortFilter")
+ for item in items
+ if s:csv_fold_headerline
+ echo printf("%02d\t% 2s\t%02d\t%10.10s\t%s",
+ \ item.id, (item.match ? '+' : '-'), item.col,
+ \ substitute(<sid>GetColumn(1, item.col),
+ \ b:col.'$', '', ''), item.orig)
+ else
+ echo printf("%02d\t% 2s\t%02d\t%s",
+ \ item.id, (item.match ? '+' : '-'),
+ \ item.col, item.orig)
+ endif
+ endfor
+ endif
+ else
+ " Reapply filter again
+ if !exists("b:csv_filter") || empty(b:csv_filter)
+ call <sid>Warn("No filters defined currently!")
+ return
+ else
+ let sid = <sid>GetSID()
+ exe 'setl foldexpr=<snr>' . sid . '_FoldValue(v:lnum,b:csv_filter)'
+ endif
+ endif
+endfu
+
+fu! <sid>SortFilter(a, b) "{{{3
+ return a:a.id == a:b.id ? 0 :
+ \ a:a.id > a:b.id ? 1 : -1
+endfu
+
+fu! <sid>GetColumn(line, col) "{{{3
+ " Return Column content at a:line, a:col
+ let a=getline(a:line)
+ " Filter comments out
+ if a =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ return ''
+ endif
+
+ if !exists("b:csv_fixed_width_cols")
+ try
+ let a = split(a, '^' . b:col . '\zs')[a:col - 1]
+ catch
+ " index out of range
+ let a = ''
+ endtry
+ else
+ let a = matchstr(a, <sid>GetColPat(a:col, 0))
+ endif
+ return substitute(a, '^\s\+\|\s\+$', '', 'g')
+endfu
+
+fu! <sid>RemoveLastItem(count) "{{{3
+ for [key,value] in items(b:csv_filter)
+ if value.id == a:count
+ call remove(b:csv_filter, key)
+ endif
+ endfor
+endfu
+
+fu! <sid>DisableFolding() "{{{3
+ setl nofen fdm=manual fdc=0 fdl=0
+ if !get(g:, 'csv_disable_fdt',0) && exists("s:fdt") && exists("s:fcs")
+ exe printf("setl fdt=%s fcs=%s", s:fdt, escape(s:fcs, '\\|'))
+ endif
+endfu
+
+fu! <sid>GetSID() "{{{3
+ if v:version > 703 || v:version == 703 && has("patch032")
+ return maparg('W', "", "", 1).sid
+ else
+ "return substitute(maparg('W'), '\(<SNR>\d\+\)_', '\1', '')
+ return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_GetSID$')
+ endif
+endfu
+
+fu! <sid>NumberFormat() "{{{3
+ let s:nr_format = [',', '.']
+ if exists("b:csv_thousands_sep")
+ let s:nr_format[0] = b:csv_thousands_sep
+ endif
+ if exists("b:csv_decimal_sep")
+ let s:nr_format[1] = b:csv_decimal_sep
+ endif
+endfu
+
+fu! <sid>CheckHeaderLine() "{{{3
+ if !exists("b:csv_headerline")
+ let s:csv_fold_headerline = 1
+ else
+ let s:csv_fold_headerline = b:csv_headerline
+ endif
+endfu
+
+fu! <sid>AnalyzeColumn(...) "{{{3
+ let maxcolnr = <SID>MaxColumns()
+ if len(a:000) == 1
+ let colnr = a:1
+ else
+ let colnr = <sid>WColumn()
+ endif
+
+ if colnr > maxcolnr
+ call <SID>Warn("There exists no column " . colnr)
+ return 1
+ endif
+
+ " Initialize s:fold_headerline
+ call <sid>CheckHeaderLine()
+ let data = <sid>CopyCol('', colnr)[s:csv_fold_headerline : -1]
+ let qty = len(data)
+ let res = {}
+ for item in data
+ if !get(res, item)
+ let res[item] = 0
+ endif
+ let res[item]+=1
+ endfor
+
+ let max_items = reverse(sort(values(res)))
+ if len(max_items) > 5
+ call remove(max_items, 5, -1)
+ call filter(res, 'v:val =~ ''^''.join(max_items, ''\|'').''$''')
+ endif
+
+ if has("float")
+ let title="Nr\tCount\t % \tValue"
+ else
+ let title="Nr\tCount\tValue"
+ endif
+ echohl "Title"
+ echo printf("%s", title)
+ echohl "Normal"
+ echo printf("%s", repeat('=', strdisplaywidth(title)))
+
+ let i=1
+ for val in max_items
+ for key in keys(res)
+ if res[key] == val && i <= len(max_items)
+ if !empty(b:delimiter)
+ let k = substitute(key, b:delimiter . '\?$', '', '')
+ else
+ let k = key
+ endif
+ if has("float")
+ echo printf("%02d\t%02d\t%2.0f%%\t%.50s", i, res[key],
+ \ ((res[key] + 0.0)/qty)*100, k)
+ else
+ echo printf("%02d\t%02d\t%.50s", i, res[key], k)
+ endif
+ call remove(res,key)
+ let i+=1
+ else
+ continue
+ endif
+ endfor
+ endfor
+ unlet max_items
+endfunc
+
+fu! <sid>Vertfold(bang, col) "{{{3
+ if a:bang
+ do Syntax
+ return
+ endif
+ if !has("conceal")
+ call <sid>Warn("Concealing not supported in your Vim")
+ return
+ endif
+ if empty(b:delimiter) && !exists("b:csv_fixed_width_cols")
+ call <sid>Warn("There are no columns defined, can't hide away anything!")
+ return
+ endif
+ if empty(a:col)
+ let colnr=<SID>WColumn()
+ else
+ let colnr=a:col
+ endif
+ let pat=<sid>GetPat(colnr, <sid>MaxColumns(), '.*')
+ if exists("b:csv_fixed_width_cols") &&
+ \ pat !~ '^\^\.\*'
+ " Make the pattern implicitly start at line start,
+ " so it will be applied by syntax highlighting (:h :syn-priority)
+ let pat='^.*' . pat
+ endif
+ let pat=substitute(pat, '\\zs\(\.\*\)\@=', '', '')
+ if !empty(pat)
+ exe "syn match CSVFold /" . pat . "/ conceal cchar=+"
+ endif
+endfu
+
+fu! <sid>InitCSVFixedWidth() "{{{3
+ if !exists("+cc")
+ " TODO: make this work with a custom matchadd() command for older
+ " Vims, that don't have 'colorcolumn'
+ call <sid>Warn("'colorcolumn' option not available")
+ return
+ endif
+ " Turn off syntax highlighting
+ syn clear
+ let _cc = &l:cc
+ let &l:cc = 1
+ redraw!
+ let list = []
+ let tcc = &l:cc
+ echo "<Cursor>, <Space>, <ESC>, <BS>, <CR>..."
+ let char=getchar()
+ while 1
+ if char == "\<Left>" || char == "\<Right>"
+ let tcc = eval('tcc'.(char=="\<Left>" ? '-' : '+').'1')
+ elseif char == "\<Space>" || char == 32 " Space
+ call add(list, tcc)
+ elseif char == "\<BS>" || char == 127
+ call remove(list, -1)
+ elseif char == "\<ESC>" || char == 27
+ let &l:cc=_cc
+ redraw!
+ return
+ else
+ break
+ endif
+ let &l:cc=tcc . (!empty(list)? ',' . join(list, ','):'')
+ redraw!
+ echo "<Cursor>, <Space>, <ESC>, <BS>, <CR>..."
+ let char=getchar()
+ endw
+ if tcc > 0
+ call add(list,tcc)
+ endif
+ let b:csv_fixed_width_cols=[]
+ let tcc=0
+ if !empty(list)
+ call Break()
+ " Remove duplicate entries
+ for val in sort(list, "<sid>SortList")
+ if val==tcc
+ continue
+ endif
+ call add(b:csv_fixed_width_cols, val)
+ let tcc=val
+ endfor
+ let b:csv_fixed_width=join(sort(b:csv_fixed_width_cols,
+ \ "<sid>SortList"), ',')
+ call <sid>Init(1, line('$'))
+ endif
+ let &l:cc=_cc
+ redraw!
+endfu
+
+fu! Break()
+ return
+endfu
+
+fu! <sid>NewRecord(line1, line2, count) "{{{3
+ if a:count =~ "\D"
+ call <sid>WarningMsg("Invalid count specified")
+ return
+ endif
+
+ let cnt = (empty(a:count) ? 1 : a:count)
+ let record = ""
+ for item in range(1,<sid>MaxColumns())
+ if !exists("b:col_width")
+ " Best guess width
+ if exists("b:csv_fixed_width_cols")
+ let record .= printf("%*s", <sid>ColWidth(item),
+ \ b:delimiter)
+ else
+ let record .= printf("%20s", b:delimiter)
+ endif
+ else
+ let record .= printf("%*s", get(b:col_width, item-1, 0)+1, b:delimiter)
+ endif
+ endfor
+
+ if getline(1)[-1:] != b:delimiter
+ let record = record[0:-2] . " "
+ endif
+
+ let line = []
+ for item in range(cnt)
+ call add(line, record)
+ endfor
+ for nr in range(a:line1, a:line2)
+ call append(nr, line)
+ endfor
+endfu
+
+fu! <sid>MoveOver(outer) "{{{3
+ " Move over a field
+ " a:outer means include the delimiter
+ let last = 0
+ let mode = a:outer
+
+ if <sid>WColumn() == <sid>MaxColumns()
+ let last = 1
+ if !mode && getline('.')[-1:] != b:delimiter
+ " No trailing delimiter, so inner == outer
+ let mode = 1
+ endif
+ endif
+ " Use the mapped key
+ exe ":sil! norm E"
+ let _s = @/
+ if last
+ exe "sil! norm! /" . b:col . "\<cr>v$h" . (mode ? "" : "\<Left>")
+ else
+ exe "sil! norm! /" . b:col . "\<cr>vn\<Left>" . (mode ? "" : "\<Left>")
+ endif
+ let @/ = _s
+endfu
+
+fu! <sid>CSVMappings() "{{{3
+ call <sid>Map('noremap', 'W', ':<C-U>call <SID>MoveCol(1, line("."))<CR>')
+ call <sid>Map('noremap', 'E', ':<C-U>call <SID>MoveCol(-1, line("."))<CR>')
+ call <sid>Map('noremap', 'K', ':<C-U>call <SID>MoveCol(0,
+ \ line(".")-v:count1)<CR>')
+ call <sid>Map('noremap', 'J', ':<C-U>call <SID>MoveCol(0,
+ \ line(".")+v:count1)<CR>')
+ call <sid>Map('nnoremap', '<CR>', ':<C-U>call <SID>PrepareFolding(1,
+ \ 1)<CR>')
+ call <sid>Map('nnoremap', '<Space>', ':<C-U>call <SID>PrepareFolding(1,
+ \ 0)<CR>')
+ call <sid>Map('nnoremap', '<BS>', ':<C-U>call <SID>PrepareFolding(0,
+ \ 1)<CR>')
+ call <sid>Map('imap', '<CR>', '<sid>ColumnMode()', 'expr')
+ " Text object: Field
+ call <sid>Map('vnoremap', 'if', ':<C-U>call <sid>MoveOver(0)<CR>')
+ call <sid>Map('vnoremap', 'af', ':<C-U>call <sid>MoveOver(1)<CR>')
+ call <sid>Map('omap', 'af', ':norm vaf<cr>')
+ call <sid>Map('omap', 'if', ':norm vif<cr>')
+ " Remap <CR> original values to a sane backup
+ call <sid>Map('noremap', '<LocalLeader>J', 'J')
+ call <sid>Map('noremap', '<LocalLeader>K', 'K')
+ call <sid>Map('vnoremap', '<LocalLeader>W', 'W')
+ call <sid>Map('vnoremap', '<LocalLeader>E', 'E')
+ call <sid>Map('noremap', '<LocalLeader>H', 'H')
+ call <sid>Map('noremap', '<LocalLeader>L', 'L')
+ call <sid>Map('nnoremap', '<LocalLeader><CR>', '<CR>')
+ call <sid>Map('nnoremap', '<LocalLeader><Space>', '<Space>')
+ call <sid>Map('nnoremap', '<LocalLeader><BS>', '<BS>')
+ call <sid>Map('map', '<C-Right>', 'W')
+ call <sid>Map('map', '<C-Left>', 'E')
+ call <sid>Map('map', 'H', 'E')
+ call <sid>Map('map', 'L', 'W')
+ call <sid>Map('map', '<Up>', 'K')
+ call <sid>Map('map', '<Down>', 'J')
+endfu
+
+fu! <sid>CommandDefinitions() "{{{3
+ call <sid>LocalCmd("WhatColumn", ':echo <sid>WColumn(<bang>0)',
+ \ '-bang')
+ call <sid>LocalCmd("NrColumns", ':call <sid>NrColumns(<q-bang>)', '-bang')
+ call <sid>LocalCmd("HiColumn", ':call <sid>HiCol(<q-args>,<bang>0)',
+ \ '-bang -nargs=?')
+ call <sid>LocalCmd("SearchInColumn",
+ \ ':call <sid>SearchColumn(<q-args>)', '-nargs=*')
+ call <sid>LocalCmd("DeleteColumn", ':call <sid>DeleteColumn(<q-args>)',
+ \ '-nargs=? -complete=custom,<sid>SortComplete')
+ call <sid>LocalCmd("ArrangeColumn",
+ \ ':call <sid>ArrangeCol(<line1>, <line2>, <bang>0)',
+ \ '-range -bang')
+ call <sid>LocalCmd("UnArrangeColumn",
+ \':call <sid>PrepUnArrangeCol(<line1>, <line2>)',
+ \ '-range')
+ call <sid>LocalCmd("InitCSV", ':call <sid>Init(<line1>,<line2>)', '-range=%')
+ call <sid>LocalCmd('Header',
+ \ ':call <sid>SplitHeaderLine(<q-args>,<bang>0,1)',
+ \ '-nargs=? -bang')
+ call <sid>LocalCmd('VHeader',
+ \ ':call <sid>SplitHeaderLine(<q-args>,<bang>0,0)',
+ \ '-nargs=? -bang')
+ call <sid>LocalCmd("HeaderToggle",
+ \ ':call <sid>SplitHeaderToggle(1)', '')
+ call <sid>LocalCmd("VHeaderToggle",
+ \ ':call <sid>SplitHeaderToggle(0)', '')
+ call <sid>LocalCmd("Sort",
+ \ ':call <sid>Sort(<bang>0, <line1>,<line2>,<q-args>)',
+ \ '-nargs=* -bang -range=% -complete=custom,<sid>SortComplete')
+ call <sid>LocalCmd("Column",
+ \ ':call <sid>CopyCol(empty(<q-reg>)?''"'':<q-reg>,<q-count>)',
+ \ '-count -register')
+ call <sid>LocalCmd("MoveColumn",
+ \ ':call <sid>MoveColumn(<line1>,<line2>,<f-args>)',
+ \ '-range=% -nargs=* -complete=custom,<sid>SortComplete')
+ call <sid>LocalCmd("SumCol",
+ \ ':echo csv#EvalColumn(<q-args>, "<sid>SumColumn", <line1>,<line2>)',
+ \ '-nargs=? -range=% -complete=custom,<sid>SortComplete')
+ call <sid>LocalCmd("ConvertData",
+ \ ':call <sid>PrepareDoForEachColumn(<line1>,<line2>,<bang>0)',
+ \ '-bang -nargs=? -range=%')
+ call <sid>LocalCmd("Filters", ':call <sid>OutputFilters(<bang>0)',
+ \ '-nargs=0 -bang')
+ call <sid>LocalCmd("Analyze", ':call <sid>AnalyzeColumn(<args>)',
+ \ '-nargs=?')
+ call <sid>LocalCmd("VertFold", ':call <sid>Vertfold(<bang>0,<q-args>)',
+ \ '-bang -nargs=? -range=% -complete=custom,<sid>SortComplete')
+ call <sid>LocalCmd("CSVFixed", ':call <sid>InitCSVFixedWidth()', '')
+ call <sid>LocalCmd("NewRecord", ':call <sid>NewRecord(<line1>,
+ \ <line2>, <q-args>)', '-nargs=? -range')
+ call <sid>LocalCmd("NewDelimiter", ':call <sid>NewDelimiter(<q-args>)',
+ \ '-nargs=1')
+ call <sid>LocalCmd("Duplicates", ':call <sid>CheckDuplicates(<q-args>)',
+ \ '-nargs=1 -complete=custom,<sid>CompleteColumnNr')
+ call <sid>LocalCmd('Transpose', ':call <sid>Transpose(<line1>, <line2>)',
+ \ '-range=%')
+ call <sid>LocalCmd('Tabularize', ':call <sid>Tabularize(<bang>0,<line1>,<line2>)',
+ \ '-bang -range=%')
+ " Alias for :Tabularize, might be taken by Tabular plugin
+ call <sid>LocalCmd('CSVTabularize', ':call <sid>Tabularize(<bang>0,<line1>,<line2>)',
+ \ '-bang -range=%')
+ call <sid>LocalCmd("AddColumn",
+ \ ':call <sid>AddColumn(<line1>,<line2>,<f-args>)',
+ \ '-range=% -nargs=* -complete=custom,<sid>SortComplete')
+ call <sid>LocalCmd('Substitute', ':call <sid>SubstituteInColumn(<q-args>,<line1>,<line2>)',
+ \ '-nargs=1 -range=%')
+endfu
+
+fu! <sid>Map(map, name, definition, ...) "{{{3
+ let keyname = substitute(a:name, '[<>]', '', 'g')
+ let expr = (exists("a:1") && a:1 == 'expr' ? '<expr>' : '')
+ if !get(g:, "csv_nomap_". tolower(keyname), 0)
+ " All mappings are buffer local
+ exe a:map "<buffer> <silent>". expr a:name a:definition
+ " should already exists
+ if a:map == 'nnoremap'
+ let unmap = 'nunmap'
+ elseif a:map == 'noremap' || a:map == 'map'
+ let unmap = 'unmap'
+ elseif a:map == 'vnoremap'
+ let unmap = 'vunmap'
+ elseif a:map == 'omap'
+ let unmap = 'ounmap'
+ elseif a:map == 'imap'
+ let unmap = 'iunmap'
+ endif
+ let b:undo_ftplugin .= "| " . unmap . " <buffer> " . a:name
+ endif
+endfu
+
+fu! <sid>LocalCmd(name, definition, args) "{{{3
+ if !exists(':'.a:name)
+ exe "com! -buffer " a:args a:name a:definition
+ let b:undo_ftplugin .= "| sil! delc " . a:name
+ endif
+ " Setup :CSV<Command> Aliases
+ if a:name !~ '^CSV'
+ call <sid>LocalCmd('CSV'.a:name, a:definition, a:args)
+ endif
+endfu
+
+fu! <sid>Menu(enable) "{{{3
+ if a:enable
+ " Make a menu for the graphical vim
+ amenu CSV.&Init\ Plugin :InitCSV<cr>
+ amenu CSV.SetUp\ &fixedwidth\ Cols :CSVFixed<cr>
+ amenu CSV.-sep1- <nul>
+ amenu &CSV.&Column.&Number :WhatColumn<cr>
+ amenu CSV.Column.N&ame :WhatColumn!<cr>
+ amenu CSV.Column.&Highlight\ column :HiColumn<cr>
+ amenu CSV.Column.&Remove\ highlight :HiColumn!<cr>
+ amenu CSV.Column.&Delete :DeleteColumn<cr>
+ amenu CSV.Column.&Sort :%Sort<cr>
+ amenu CSV.Column.&Copy :Column<cr>
+ amenu CSV.Column.&Move :%MoveColumn<cr>
+ amenu CSV.Column.S&um :%SumCol<cr>
+ amenu CSV.Column.Analy&ze :Analyze<cr>
+ amenu CSV.Column.&Arrange :%ArrangeCol<cr>
+ amenu CSV.Column.&UnArrange :%UnArrangeCol<cr>
+ amenu CSV.Column.&Add :%AddColumn<cr>
+ amenu CSV.-sep2- <nul>
+ amenu CSV.&Toggle\ Header :HeaderToggle<cr>
+ amenu CSV.&ConvertData :ConvertData<cr>
+ amenu CSV.Filters :Filters<cr>
+ amenu CSV.Hide\ C&olumn :VertFold<cr>
+ amenu CSV.&New\ Record :NewRecord<cr>
+ else
+ " just in case the Menu wasn't defined properly
+ sil! amenu disable CSV
+ endif
+endfu
+
+fu! <sid>SaveOptions(list) "{{{3
+ let save = {}
+ for item in a:list
+ exe "let save.". item. " = &l:". item
+ endfor
+ return save
+endfu
+
+fu! <sid>NewDelimiter(newdelimiter) "{{{3
+ let save = <sid>SaveOptions(['ro', 'ma'])
+ if exists("b:csv_fixed_width_cols")
+ call <sid>Warn("NewDelimiter does not work with fixed width column!")
+ return
+ endif
+ if !&l:ma
+ setl ma
+ endif
+ if &l:ro
+ setl noro
+ endif
+ let line=1
+ while line <= line('$')
+ " Don't change delimiter for comments
+ if getline(line) =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ let line+=1
+ continue
+ endif
+ let fields=split(getline(line), b:col . '\zs')
+ " Remove field delimiter
+ call map(fields, 'substitute(v:val, b:delimiter .
+ \ ''\?$'' , "", "")')
+ call setline(line, join(fields, a:newdelimiter))
+ let line+=1
+ endwhile
+ " reset local buffer options
+ for [key, value] in items(save)
+ call setbufvar('', '&'. key, value)
+ endfor
+ "reinitialize the plugin
+ call <sid>Init(1,line('$'))
+endfu
+
+fu! <sid>IN(list, value) "{{{3
+ for item in a:list
+ if item == a:value
+ return 1
+ endif
+ endfor
+ return 0
+endfu
+
+fu! <sid>DuplicateRows(columnlist) "{{{3
+ let duplicates = {}
+ let cnt = 0
+ let line = 1
+ while line <= line('$')
+ let key = ""
+ let i = 1
+ let content = getline(line)
+ " Skip comments
+ if content =~ '^\s*\V'. escape(b:csv_cmt[0], '\\')
+ continue
+ endif
+ let cols = split(content, b:col. '\zs')
+ for column in cols
+ if <sid>IN(a:columnlist, i)
+ let key .= column
+ endif
+ let i += 1
+ endfor
+ if has_key(duplicates, key) && cnt < 10
+ call <sid>Warn("Duplicate Row ". line)
+ let cnt += 1
+ elseif has_key(duplicates, key)
+ call <sid>Warn("More duplicate Rows after: ". line)
+ call <sid>Warn("Aborting...")
+ return
+ else
+ let duplicates[key] = 1
+ endif
+ let line += 1
+ endwhile
+ if cnt == 0
+ call <sid>Warn("No Duplicate Row found!")
+ endif
+endfu
+fu! <sid>CompleteColumnNr(A,L,P) "{{{3
+ return join(range(1,<sid>MaxColumns()), "\n")
+endfu
+
+fu! <sid>CheckDuplicates(list) "{{{3
+ let string = a:list
+ if string =~ '\d\s\?-\s\?\d'
+ let string = substitute(string, '\(\d\+\)\s\?-\s\?\(\d\+\)',
+ \ '\=join(range(submatch(1),submatch(2)), ",")', '')
+ endif
+ let list=split(string, ',')
+ call <sid>DuplicateRows(list)
+endfu
+
+fu! <sid>Transpose(line1, line2) "{{{3
+ " Note: - Comments will be deleted.
+ " - Does not work with fixed-width columns
+ if exists("b:csv_fixed_width")
+ call <sid>Warn("Transposing does not work with fixed-width columns!")
+ return
+ endif
+ let _wsv = winsaveview()
+ let TrailingDelim = 0
+
+ if line('$') > 1
+ let TrailingDelim = getline(1) =~ b:delimiter.'$'
+ endif
+
+ let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
+
+ try
+ let columns = <sid>MaxColumns(a:line1)
+ catch
+ " No column, probably because of comment or empty line
+ " so use the number of columns from the beginning of the file
+ let columns = <sid>MaxColumns()
+ endtry
+ let matrix = []
+ for line in range(a:line1, a:line2)
+ " Filter comments out
+ if getline(line) =~ pat
+ continue
+ endif
+ let r = []
+ for row in range(1,columns)
+ let field = <sid>GetColumn(line, row)
+ call add(r, field)
+ endfor
+ call add(matrix, r)
+ endfor
+ unlet row
+
+ " create new transposed matrix
+ let transposed = []
+ for row in matrix
+ let i = 0
+ for val in row
+ if get(transposed, i, []) == []
+ call add(transposed, [])
+ endif
+ if val[-1:] != b:delimiter
+ let val .= b:delimiter
+ endif
+ call add(transposed[i], val)
+ let i+=1
+ endfor
+ endfor
+ " Save memory
+ unlet! matrix
+ call map(transposed, 'join(v:val, '''')')
+ if !TrailingDelim
+ call map(transposed, 'substitute(v:val, b:delimiter.''\?$'', "", "")')
+ endif
+ " filter out empty records
+ call filter(transposed, 'v:val != b:delimiter')
+
+ " Insert transposed data
+ let delete_last_line = 0
+ if a:line1 == 1 && a:line2 == line('$')
+ let delete_last_line = 1
+ endif
+ exe a:line1. ",". a:line2. "d _"
+ let first = (a:line1 > 0 ? (a:line1 - 1) : 0)
+ call append(first, transposed)
+ if delete_last_line
+ sil $d _
+ endif
+ " save memory
+ unlet! transposed
+ call winrestview(_wsv)
+endfu
+
+
+fu! <sid>NrColumns(bang) "{{{3
+ if !empty(a:bang)
+ try
+ let cols = <sid>MaxColumns(line('.'))
+ catch
+ " No column or comment line
+ call <sid>Warn("No valid CSV Column!")
+ endtry
+ else
+ let cols = <sid>MaxColumns()
+ endif
+ echo cols
+endfu
+
+fu! <sid>Tabularize(bang, first, last) "{{{3
+ let _c = winsaveview()
+ " Table delimiter definition "{{{4
+ if !exists("s:td")
+ let s:td = {
+ \ 'hbar': (&enc =~# 'utf-8' ? '─' : '-'),
+ \ 'vbar': (&enc =~# 'utf-8' ? '│' : '|'),
+ \ 'scol': (&enc =~# 'utf-8' ? '├' : '|'),
+ \ 'ecol': (&enc =~# 'utf-8' ? '┤' : '|'),
+ \ 'ltop': (&enc =~# 'utf-8' ? '┌' : '+'),
+ \ 'rtop': (&enc =~# 'utf-8' ? '┐' : '+'),
+ \ 'lbot': (&enc =~# 'utf-8' ? '└' : '+'),
+ \ 'rbot': (&enc =~# 'utf-8' ? '┘' : '+'),
+ \ 'cros': (&enc =~# 'utf-8' ? '┼' : '+'),
+ \ 'dhor': (&enc =~# 'utf-8' ? '┬' : '-'),
+ \ 'uhor': (&enc =~# 'utf-8' ? '┴' : '-')
+ \ }
+ endif "}}}4
+ if match(getline(a:first), '^'.s:td.ltop) > -1
+ " Already tabularized, done
+ call <sid>Warn("Looks already Tabularized, aborting!")
+ return
+ endif
+ let _ma = &l:ma
+ setl ma
+ let colwidth = 0
+ let adjust_last = 0
+ call cursor(a:first,0)
+ call <sid>CheckHeaderLine()
+ if exists("b:csv_fixed_width_cols")
+ let cols=copy(b:csv_fixed_width_cols)
+ let pat = join(map(cols, ' ''\(\%''. v:val. ''c\)'' '), '\|')
+ let colwidth = strlen(substitute(getline('$'), '.', 'x', 'g'))
+ let t=-1
+ let b:col_width = []
+ for item in b:csv_fixed_width_cols + [colwidth]
+ if t > -1
+ call add(b:col_width, item-t)
+ endif
+ let t = item
+ endfor
+ else
+ " don't clear column width variable, might have been set in the
+ " plugin!
+ sil call <sid>ArrangeCol(a:first, a:last, 0)
+ endif
+
+ if empty(b:col_width)
+ call <sid>Warn('An error occured, aborting!')
+ return
+ endif
+ let b:col_width[-1] += 1
+ let marginline = s:td.scol. join(map(copy(b:col_width), 'repeat(s:td.hbar, v:val)'), s:td.cros). s:td.ecol
+
+ exe printf('sil %d,%ds/%s/%s/ge', a:first, (a:last+adjust_last),
+ \ (exists("b:csv_fixed_width_cols") ? pat : b:delimiter ), s:td.vbar)
+ " Add vertical bar in first column, if there isn't already one
+ exe printf('sil %d,%ds/%s/%s/e', a:first, a:last+adjust_last,
+ \ '^[^'. s:td.vbar. s:td.scol. ']', s:td.vbar.'&')
+ " And add a final vertical bar, if there isn't already
+ exe printf('sil %d,%ds/%s/%s/e', a:first, a:last+adjust_last,
+ \ '[^'. s:td.vbar. s:td.ecol. ']$', '&'. s:td.vbar)
+ " Make nice intersection graphs
+ let line = split(getline(a:first), s:td.vbar)
+ call map(line, 'substitute(v:val, ''[^''.s:td.vbar. '']'', s:td.hbar, ''g'')')
+ " Set top and bottom margins
+ call append(a:first-1, s:td.ltop. join(line, s:td.dhor). s:td.rtop)
+ call append(a:last+adjust_last+1, s:td.lbot. join(line, s:td.uhor). s:td.rbot)
+
+ if s:csv_fold_headerline > 0 && !a:bang
+ "call <sid>NewRecord(s:csv_fold_headerline, s:csv_fold_headerline, 1)
+ call append(a:first + s:csv_fold_headerline, marginline)
+ let adjust_last += 1
+ endif
+
+ if a:bang
+ exe printf('sil %d,%ds/^%s\zs\n/&%s&/e', a:first + s:csv_fold_headerline, a:last + adjust_last,
+ \ '[^'.s:td.scol. '][^'.s:td.hbar.'].*', marginline)
+ endif
+
+ syn clear
+ let &l:ma = _ma
+ call winrestview(_c)
+endfu
+
+fu! <sid>SubstituteInColumn(command, line1, line2) range "{{{3
+ " Command can be something like 1,2/foobar/foobaz/ to replace in 1 and second column
+ " Command can be something like /foobar/foobaz/ to replace in the current column
+ " Command can be something like 1,$/foobar/foobaz/ to replace in all columns
+ " Command can be something like 3/foobar/foobaz/flags to replace only in the 3rd column
+
+ " Save position and search register
+ let _wsv = winsaveview()
+ let _search = [ '/', getreg('/'), getregtype('/')]
+ let columns = []
+ let maxcolnr = <sid>MaxColumns()
+ let simple_s_command = 0 " when set to 1, we can simply use an :s command
+
+ " try to split on '/' if it is not escaped or in a collection
+ let cmd = split(a:command, '\%([\\]\|\[[^]]*\)\@<!/')
+ if a:command !~? '^\%([$]\|\%(\d\+\)\%(,\%([0-9]\+\|[$]\)\)\?\)/' ||
+ \ len(cmd) == 2 ||
+ \ ((len(cmd) == 3 && cmd[2] =~# '^[&cgeiInp#l]\+$'))
+ " No Column address given
+ call add(columns, <sid>WColumn())
+ let cmd = [columns[0]] + cmd "First item of cmd list contains address!
+ elseif ((len(cmd) == 3 && cmd[2] !~# '^[&cgeiInp#l]\+$')
+ \ || len(cmd) == 4)
+ " command could be '1/foobbar/foobaz'
+ " but also 'foobar/foobar/g'
+ let columns = split(cmd[0], ',')
+ if empty(columns)
+ " No columns given? replace in current column only
+ let columns[0] = <sid>WColumn()
+ elseif columns[-1] == '$'
+ let columns[-1] = maxcolnr
+ endif
+ else " not reached ?
+ call add(columns, <sid>WColumn())
+ endif
+
+ try
+ if len(cmd) == 1 || columns[0] =~ '\D' || (len(columns) == 2 && columns[1] =~ '\D')
+ call <SID>Warn("Error! Usage :S [columns/]pattern/replace[/flags]")
+ return
+ endif
+
+ if len(columns) == 2 && columns[0] == 1 && columns[1] == maxcolnr
+ let simple_s_command = 1
+ elseif len(columns) == 2
+ let columns = range(columns[0], columns[1])
+ endif
+
+ let has_flags = len(cmd) == 4
+
+ if simple_s_command
+ while search(cmd[1])
+ exe printf("%d,%ds/%s/%s%s", a:line1, a:line2, cmd[1], cmd[2], (has_flags ? '/'. cmd[3] : ''))
+ if !has_flags || (has_flags && cmd[3] !~# 'g')
+ break
+ endif
+ endw
+ else
+ for colnr in columns
+ let @/ = <sid>GetPat(colnr, maxcolnr, cmd[1])
+ while search(@/)
+ exe printf("%d,%ds//%s%s", a:line1, a:line2, cmd[2], (has_flags ? '/'. cmd[3] : ''))
+ if !has_flags || (has_flags && cmd[3] !~# 'g')
+ break
+ endif
+ endw
+ endfor
+ endif
+ catch /^Vim\%((\a\+)\)\=:E486/
+ " Pattern not found
+ echohl Error
+ echomsg "E486: Pattern not found in column " . colnr . ": " . pat
+ if &vbs > 0
+ echomsg substitute(v:exception, '^[^:]*:', '','')
+ endif
+ echohl Normal
+ catch
+ echohl Error
+ "if &vbs > 0
+ echomsg substitute(v:exception, '^[^:]*:', '','')
+ "endif
+ echohl Normal
+ finally
+ " Restore position and search register
+ call winrestview(_wsv)
+ call call('setreg', _search)
+ endtry
+endfu
+
+fu! <sid>ColumnMode() "{{{3
+ if mode() =~# 'R'
+ " (virtual) Replace mode
+ let new_line = (line('.') == line('$') ||
+ \ (synIDattr(synIDtrans(synID(line("."), col("."), 1)), "name") !~# "comment"))
+ return "\<ESC>". (new_line ? "o" : "JE".mode())
+ else
+ return "\<CR>"
+ endif
+endfu
+
+" Global functions "{{{2
+fu! csv#EvalColumn(nr, func, first, last) range "{{{3
+ let save = winsaveview()
+ call <sid>CheckHeaderLine()
+ let nr = matchstr(a:nr, '^\d\+')
+ let col = (empty(nr) ? <sid>WColumn() : nr)
+ " don't take the header line into consideration
+ let start = a:first - 1 + s:csv_fold_headerline
+ let stop = a:last - 1 + s:csv_fold_headerline
+
+ let column = <sid>CopyCol('', col)[start : stop]
+ " Delete delimiter
+ call map(column, 'substitute(v:val, b:delimiter . "$", "", "g")')
+ " Revmoe trailing whitespace
+ call map(column, 'substitute(v:val, ''^\s\+$'', "", "g")')
+ " Remove leading whitespace
+ call map(column, 'substitute(v:val, ''^\s\+'', "", "g")')
+ " Delete empty values
+ " Leave this up to the function that does something
+ " with each value
+ "call filter(column, '!empty(v:val)')
+
+ " parse the optional number format
+ let format = matchstr(a:nr, '/[^/]*/')
+ call <sid>NumberFormat()
+ if !empty(format)
+ try
+ let s = []
+ " parse the optional number format
+ let str = matchstr(format, '/\zs[^/]*\ze/', 0, start)
+ let s = matchlist(str, '\(.\)\?:\(.\)\?')[1:2]
+ if empty(s)
+ " Number format wrong
+ call <sid>Warn("Numberformat wrong, needs to be /x:y/!")
+ return ''
+ endif
+ if !empty(s[0])
+ let s:nr_format[0] = s[0]
+ endif
+ if !empty(s[1])
+ let s:nr_format[1] = s[1]
+ endif
+ endtry
+ endif
+ try
+ let result=call(function(a:func), [column])
+ return result
+ catch
+ " Evaluation of expression failed
+ echohl Title
+ echomsg "Evaluating" matchstr(a:func, '[a-zA-Z]\+$')
+ \ "failed for column" col . "!"
+ echohl Normal
+ return ''
+ finally
+ call winrestview(save)
+ endtry
+endfu
+
+" return field index (x,y) with leading/trailing whitespace and trailing
+" delimiter stripped (only when a:0 is not given)
+fu! CSVField(x, y, ...) "{{{3
+ if &ft != 'csv'
+ return
+ endif
+ let y = a:y - 1
+ let x = (a:x < 0 ? 0 : a:x)
+ let orig = !empty(a:0)
+ let y = (y < 0 ? 0 : y)
+ let x = (x > (<sid>MaxColumns()) ? (<sid>MaxColumns()) : x)
+ let col = <sid>CopyCol('',x)
+ if !orig
+ " remove leading and trainling whitespace and the delimiter
+ return matchstr(col[y], '^\s*\zs.\{-}\ze\s*'.b:delimiter.'\?$')
+ else
+ return col[y]
+ endif
+endfu
+" return current column number (if a:0 is given, returns the name
+fu! CSVCol(...) "{{{3
+ return <sid>WColumn(a:0)
+endfu
+fu! CSVPat(colnr, ...) "{{{3
+ " Make sure, we are working in a csv file
+ if &ft != 'csv'
+ return ''
+ endif
+ " encapsulates GetPat(), that returns the search pattern for a
+ " given column and tries to set the cursor at the specific position
+ let pat = <sid>GetPat(a:colnr, <SID>MaxColumns(), a:0 ? a:1 : '.*')
+ "let pos = match(pat, '.*\\ze') + 1
+ " Try to set the cursor at the beginning of the pattern
+ " does not work
+ "call setcmdpos(pos)
+ return pat
+endfu
+
+fu! CSV_WCol(...) "{{{3
+ try
+ if exists("a:1") && (a:1 == 'Name' || a:1 == 1)
+ return printf("%s", <sid>WColumn(1))
+ else
+ return printf(" %d/%d", <SID>WColumn(), <SID>MaxColumns())
+ endif
+ catch
+ return ''
+ endtry
+endfun
+
+" Initialize Plugin "{{{2
+let b:csv_start = exists("b:csv_start") ? b:csv_start : 1
+let b:csv_end = exists("b:csv_end") ? b:csv_end : line('$')
+
+call <SID>Init(b:csv_start, b:csv_end)
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" Vim Modeline " {{{2
+" vim: set foldmethod=marker et:
diff --git a/syntax/csv.vim b/syntax/csv.vim
new file mode 100644
index 00000000..41a194d1
--- /dev/null
+++ b/syntax/csv.vim
@@ -0,0 +1,188 @@
+" A simple syntax highlighting, simply alternate colors between two
+" adjacent columns
+" Init {{{2
+let s:cpo_save = &cpo
+set cpo&vim
+
+scriptencoding utf8
+if version < 600
+ syn clear
+elseif exists("b:current_syntax")
+ finish
+endif
+
+" Helper functions "{{{2
+fu! <sid>Warning(msg) "{{{3
+ " Don't redraw, so we are not overwriting messages from the ftplugin
+ " script
+ "redraw!
+ echohl WarningMsg
+ echomsg "CSV Syntax:" . a:msg
+ echohl Normal
+endfu
+
+fu! <sid>Esc(val, char) "{{2
+ return '\V'.escape(a:val, '\\'.a:char).'\m'
+endfu
+
+fu! <sid>CheckSaneSearchPattern() "{{{3
+ let s:del_def = ','
+ let s:col_def = '\%([^' . s:del_def . ']*' . s:del_def . '\|$\)'
+ let s:col_def_end = '\%([^' . s:del_def . ']*' . s:del_def . '\)'
+
+ " First:
+ " Check for filetype plugin. This syntax script relies on the filetype
+ " plugin, else, it won't work properly.
+ redir => s:a |sil filetype | redir end
+ let s:a=split(s:a, "\n")[0]
+ if match(s:a, '\cplugin:off') > 0
+ call <sid>Warning("No filetype support, only simple highlighting using"
+ \ . s:del_def . " as delimiter! See :h csv-installation")
+ endif
+
+ " Check Comment setting
+ if !exists("g:csv_comment")
+ let b:csv_cmt = split(&cms, '%s')
+ else
+ let b:csv_cmt = split(g:csv_comment, '%s')
+ endif
+
+
+ " Second: Check for sane defaults for the column pattern
+ " Not necessary to check for fixed width columns
+ if exists("b:csv_fixed_width_cols")
+ return
+ endif
+
+
+ " Try a simple highlighting, if the defaults from the ftplugin
+ " don't exist
+ let s:col = exists("b:col") && !empty(b:col) ? b:col
+ \ : s:col_def
+ let s:col_end = exists("b:col_end") && !empty(b:col_end) ? b:col_end
+ \ : s:col_def_end
+ let s:del = exists("b:delimiter") && !empty(b:delimiter) ? b:delimiter
+ \ : s:del_def
+ let s:cmts = exists("b:csv_cmt") ? b:csv_cmt[0] : split(&cms, '&s')[0]
+ let s:cmte = exists("b:csv_cmt") && len(b:csv_cmt) == 2 ? b:csv_cmt[1]
+ \ : ''
+
+ if line('$') > 1 && (!exists("b:col") || empty(b:col))
+ " check for invalid pattern, ftplugin hasn't been loaded yet
+ call <sid>Warning("Invalid column pattern, using default pattern " . s:col_def)
+ endif
+endfu
+
+" Syntax rules {{{2
+fu! <sid>DoHighlight() "{{{3
+ if has("conceal") && !exists("g:csv_no_conceal") &&
+ \ !exists("b:csv_fixed_width_cols")
+ " old val
+ "\ '\%(.\)\@=/ms=e,me=e contained conceal cchar=' .
+ " Has a problem with the last line!
+ exe "syn match CSVDelimiter /" . s:col_end .
+ \ '/ms=e,me=e contained conceal cchar=' .
+ \ (&enc == "utf-8" ? "│" : '|')
+ "exe "syn match CSVDelimiterEOL /" . s:del .
+ " \ '\?$/ contained conceal cchar=' .
+ " \ (&enc == "utf-8" ? "│" : '|')
+ hi def link CSVDelimiter Conceal
+ elseif !exists("b:csv_fixed_width_cols")
+ " The \%(.\)\@<= makes sure, the last char won't be concealed,
+ " if it isn't a delimiter
+ "exe "syn match CSVDelimiter /" . s:col . '\%(.\)\@<=/ms=e,me=e contained'
+ exe "syn match CSVDelimiter /" . s:col_end . '/ms=e,me=e contained'
+ "exe "syn match CSVDelimiterEOL /" . s:del . '\?$/ contained'
+ if has("conceal")
+ hi def link CSVDelimiter Conceal
+ else
+ hi def link CSVDelimiter Ignore
+ endif
+ endif " There is no delimiter for csv fixed width columns
+
+
+ if !exists("b:csv_fixed_width_cols")
+ exe 'syn match CSVColumnEven nextgroup=CSVColumnOdd /'
+ \ . s:col . '/ contains=CSVDelimiter'
+ exe 'syn match CSVColumnOdd nextgroup=CSVColumnEven /'
+ \ . s:col . '/ contains=CSVDelimiter'
+
+ exe 'syn match CSVColumnHeaderEven nextgroup=CSVColumnHeaderOdd /\%1l'
+ \. s:col . '/ contains=CSVDelimiter'
+ exe 'syn match CSVColumnHeaderOdd nextgroup=CSVColumnHeaderEven /\%1l'
+ \. s:col . '/ contains=CSVDelimiter'
+ else
+ for i in range(len(b:csv_fixed_width_cols))
+ let pat = '/\%' . b:csv_fixed_width_cols[i] . 'c.*' .
+ \ ((i == len(b:csv_fixed_width_cols)-1) ? '/' :
+ \ '\%' . b:csv_fixed_width_cols[i+1] . 'c/')
+
+ let group = "CSVColumn" . (i%2 ? "Odd" : "Even" )
+ let ngroup = "CSVColumn" . (i%2 ? "Even" : "Odd" )
+ exe "syn match " group pat " nextgroup=" . ngroup
+ endfor
+ endif
+ " Comment regions
+ exe 'syn match CSVComment /'. <sid>Esc(s:cmts, '/'). '.*'.
+ \ (!empty(s:cmte) ? '\%('. <sid>Esc(s:cmte, '/'). '\)\?'
+ \: ''). '/'
+ hi def link CSVComment Comment
+endfun
+
+fu! <sid>DoSyntaxDefinitions() "{{{3
+ syn spell toplevel
+
+ " Not really needed
+ syn case ignore
+
+ hi def link CSVColumnHeaderOdd WarningMsg
+ hi def link CSVColumnHeaderEven WarningMsg
+ hi def link CSVColumnOdd DiffAdd
+ hi def link CSVColumnEven DiffChange
+ " Old Version
+ if 0
+ if &t_Co < 88
+ if !exists("b:csv_fixed_width_cols")
+ hi default CSVColumnHeaderOdd ctermfg=DarkRed ctermbg=15
+ \ guibg=grey80 guifg=black term=underline cterm=standout,bold
+ \ gui=bold,underline
+ endif
+ hi default CSVColumnOdd ctermfg=DarkRed ctermbg=15 guibg=grey80
+ \ guifg=black term=underline cterm=bold gui=underline
+ else
+ if !exists("b:csv_fixed_width_cols")
+ hi default CSVColumnHeaderOdd ctermfg=darkblue ctermbg=white
+ \ guibg=grey80 guifg=black cterm=standout,underline
+ \ gui=bold,underline
+ endif
+ hi default CSVColumnOdd ctermfg=darkblue ctermbg=white guibg=grey80
+ \ guifg=black cterm=reverse,underline gui=underline
+ endif
+
+ " ctermbg=8 should be safe, even in 8 color terms
+ if !exists("b:csv_fixed_width_cols")
+ hi default CSVColumnHeaderEven ctermfg=white ctermbg=darkgrey
+ \ guibg=grey50 guifg=black term=bold cterm=standout,underline
+ \ gui=bold,underline
+ endif
+ hi default CSVColumnEven ctermfg=white ctermbg=darkgrey guibg=grey50
+ \ guifg=black term=bold cterm=underline gui=bold,underline
+ endif
+endfun
+
+" Main: {{{2
+" Make sure, we are using a sane, valid pattern for syntax
+" highlighting
+call <sid>CheckSaneSearchPattern()
+
+" Define all necessary syntax groups
+call <sid>DoSyntaxDefinitions()
+
+" Highlight the file
+call <sid>DoHighlight()
+
+" Set the syntax variable {{{2
+let b:current_syntax="csv"
+
+let &cpo = s:cpo_save
+unlet s:cpo_save