Vim related configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

4981 lines
175 KiB

" Vimball Archiver by Charles E. Campbell, Jr., Ph.D.
UseVimball
finish
ftplugin/csv.vim [[[1
2819
" Filetype plugin for editing CSV files. "{{{1
" Author: Christian Brabandt <cb@256bit.org>
" Version: 0.31
" Script: http://www.vim.org/scripts/script.php?script_id=2830
" License: VIM License
" Last Change: Thu, 15 Jan 2015 21:05:10 +0100
" Documentation: see :help ft-csv.txt
" GetLatestVimScripts: 2830 30 :AutoInstall: csv.vim
"
" Some ideas are taken from the wiki http://vim.wikia.com/wiki/VimTip667
" though, implementation differs.
" Plugin folklore "{{{2
fu! <sid>DetermineSID()
let s:SID = matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_DetermineSID$')
endfu
call s:DetermineSID()
delf s:DetermineSID
let s:numeric_sort = v:version > 704 || v:version == 704 && has("patch341")
if !s:numeric_sort
fu! <sid>CSVSortValues(i1, i2) "{{{3
return (a:i1+0) == (a:i2+0) ? 0 : (a:i1+0) > (a:i2+0) ? 1 : -1
endfu
endif
fu! CSVArrangeCol(first, last, bang, limit) range "{{{2
if &ft =~? 'csv'
call <sid>ArrangeCol(a:first, a:last, a:bang, a:limit)
else
finish
endif
endfu
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
" if a:1 is set, keep the b:delimiter
let keep = exists("a:1") && a:1
" 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 !keep
if !exists("g:csv_delim")
let b:delimiter=<SID>GetDelimiter(a:startline, a:endline)
else
let b:delimiter=g:csv_delim
endif
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*\)' . 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 b:csv_cmt"
\ . "| unlet! b:csv_thousands_sep b:csv_decimal_sep"
\. " | unlet! b:browsefilter b:csv_cmt"
\. " | unlet! b:csv_arrange_leftalign"
" Delete all functions
" disabled currently, because otherwise when switching ft
" I think, all functions need to be read in again and this
" costs time.
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
let b:browsefilter="CSV Files (*.csv, *.dat)\t*.csv;*.dat\n".
\ "All Files\t*.*\n"
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!"'
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
endif
endfu
fu! <sid>GetPat(colnr, maxcolnr, pat, allowmore) "{{{3
" if a:allowmmore, allows more to match after the pattern
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' .
\ (a:allowmore ? ('\%([^' . 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) .
\ '\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 .
\ (a:allowmore ? ('\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
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. '\)', 1)
try
" force redraw, so that the search pattern isn't shown
exe "norm! n\<c-l>"
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')
" remove matches, that come from matchaddpos()
" setmatches() can't handle them.
call filter(matchlist, 'has_key(v:val, "pattern")')
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: ' ', 4: '\^'}
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")
if line('.') > 1 && mode('') != 'n'
" in insert mode, get line from above, just in case the current
" line is empty
let line = getline(line('.')-1)
else
let line=getline('.')
endif
" 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: Try to get the column name
let head = split(getline(1),b:col.'\zs')
" remove preceeding whitespace
if len(head) < ret
call <sid>Warn("Header has no field ". ret)
else
let ret = substitute(head[ret-1], '^\s\+', '', '')
" remove delimiter
let ret = substitute(ret, b:delimiter. '$', '', '')
endif
endif
else
let temp=getpos('.')[2]
let j=1
let ret = 1
for i in sort(b:csv_fixed_width_cols, s:numeric_sort ? 'n' : 's:CSVSortValues')
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
" if a:1 is given, specifies the row, for which to calculate the width
"
" 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")
" only check first 10000 lines, to be faster
let last = line('$')
if exists("a:1")
let last = a:1
endif
if !get(b:, 'csv_arrange_use_all_rows', 0)
if last > 10000
let last = 10000
call <sid>Warn('File too large, only checking the first 10000 rows for the width')
endif
endif
let b:csv_list=getline(1,last)
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]')
" do not strip leading whitespace
" call map(tlist, 'substitute(v:val, ''^\s*'', "", "g")')
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, limit, ...) range "{{{3
" a:1, optional width parameter of line from which to take the width
"
" 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("a:1") && !empty(a:1))
" Force recalculating the Column width
unlet! b:csv_list b:col_width
elseif a:limit > -1 && a:limit < getfsize(fnamemodify(bufname(''), ':p'))
return
endif
if !exists("b:col_width")
" Force recalculation of Column width
let row = exists("a:1") ? a:1 : ''
call <sid>CalculateColumnWidth(row)
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
let s:count = 0
let _stl = &stl
let s:max = (a:last - a:first + 1) * len(b:col_width)
let s:temp = 0
try
exe "sil". a:first . ',' . a:last .'s/' . (b:col) .
\ '/\=<SID>Columnize(submatch(0))/' . (&gd ? '' : 'g')
finally
" Clean up variables, that were only needed for <sid>Columnize() function
unlet! s:columnize_count s:max_cols s:prev_line s:max s:count s:temp s:val
if ro
setl ro
unlet ro
endif
let &stl = _stl
call winrestview(cur)
endtry
endfu
fu! <sid>ProgressBar(cnt, max) "{{{3
if get(g:, 'csv_no_progress', 0)
return
endif
let width = 40 " max width of progressbar
if width > &columns
let width = &columns
endif
let s:val = a:cnt * width / a:max
if (s:val > s:temp || a:cnt==1)
let &stl='%#DiffAdd#['.repeat('=', s:val).'>'. repeat(' ', width-s:val).']'.
\ (width < &columns ? ' '.100*s:val/width. '%%' : '')
redrawstatus
let s:temp = s:val
endif
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:
"if get(b:, 'csv_arrange_leftalign',0)
" return substitute(a:match, '\s\+\ze'. b:delimiter. '\?$', '', '')
"else
" return substitute(a:match, '^\s\+', '', '')
"endif
return substitute(a:match, '\%(^\s\+\)\|\%(\s\+\ze'.b:delimiter. '\?$\)', '', 'g')
" only strip leading white space, if a non-white space follows:
"return substitute(a:match, '^\s\+\ze\S', '', '')
endfu
fu! <sid>CalculateColumnWidth(row) "{{{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)
if empty(a:row)
call add(b:col_width, <SID>ColWidth(i))
else
call add(b:col_width, <SID>ColWidth(i,a:row))
endif
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 s:columnize_count s:decimal_column
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:count+=1
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 colnr = s:columnize_count % s:max_cols
let width = get(b:col_width, colnr, 20)
let align = 'r'
if exists('b:csv_arrange_align')
let align_list=split(get(b:, 'csv_arrange_align', " "), '\zs')
try
let align = align_list[colnr]
catch
let align = 'r'
endtry
endif
if ((align isnot? 'r' && align isnot? 'l' &&
\ align isnot? 'c' && align isnot? '.') || get(b:, 'csv_arrange_leftalign', 0))
let align = 'r'
endif
call <sid>ProgressBar(s:count,s:max)
let s:columnize_count += 1
let has_delimiter = (a:field[-1:] is? b:delimiter)
if align is? 'l'
" left-align content
return printf("%-*S%s", width+1 ,
\ (has_delimiter ? a:field[:-2] : a:field),
\ (has_delimiter ? b:delimiter : ' '))
elseif align is? 'c'
" center the column
let t = width - len(split(a:field, '\zs'))
let leftwidth = t/2
" uneven width, add one
let rightwidth = (t%2 ? leftwidth+1 : leftwidth)
let field = (has_delimiter ? a:field[:-2] : a:field). repeat(' ', rightwidth)
return printf("%*S%s", width , field, (has_delimiter ? b:delimiter : ' '))
elseif align is? '.'
if !exists("s:decimal_column")
let s:decimal_column = {}
endif
if get(s:decimal_column, colnr, 0) == 0
call <sid>CheckHeaderLine()
call <sid>NumberFormat()
let data = <sid>CopyCol('', colnr+1, '')[s:csv_fold_headerline : -1]
let pat1 = escape(s:nr_format[1], '.').'\zs[^'.s:nr_format[1].']*\ze'.
\ (has_delimiter ? b:delimiter : '').'$'
let pat2 = '\d\+\ze\%(\%('.escape(s:nr_format[1], '.'). '\d\+\)\|'.
\ (has_delimiter ? b:delimiter : '').'$\)'
let data1 = map(copy(data), 'matchstr(v:val, pat1)')
let data2 = map(data, 'matchstr(v:val, pat2)')
" strlen should be okay for decimals...
let data1 = map(data1, 'strlen(v:val)')
let data2 = map(data2, 'strlen(v:val)')
let dec = max(data1)
let scal = max(data2)
if dec + scal + 1 + (has_delimiter ? 1 : 0) > width
let width = dec + scal + 1 + (has_delimiter ? 1 :0)
let b:col_width[colnr] = width
endif
let s:decimal_column[colnr] = dec
else
let dec = get(s:decimal_column, colnr)
endif
let field = (has_delimiter ? a:field[:-2] : a:field)
let fmt = printf("%%%d.%df", width+1, dec)
try
if s:nr_format[1] isnot '.'
let field = substitute(field, s:nr_format[1], '.', 'g')
let field = substitute(field, s:nr_format[0], '', 'g')
endif
if field =~? '\h' " text in the column, can't be converted to float
throw "no decimal"
endif
let result = printf(fmt, str2float(field)). (has_delimiter ? b:delimiter : ' ')
catch
let result = printf("%*S", width+2, a:field)
endtry
return result
else
" right align
return printf("%*S", width+1 , a:field)
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] . 'v.*'
else
let pat='\%' . b:csv_fixed_width_cols[(a:colnr - 1)] .
\ 'c.\{-}\%' . b:csv_fixed_width_cols[a:colnr] . 'v'
endif
endif
elseif !exists("b:csv_fixed_width_cols")
let pat=b:col
else
let pat='\%' . b:csv_fixed_width_cols[0] . 'v.\{-}' .
\ (len(b:csv_fixed_width_cols) > 1 ?
\ '\%' . b:csv_fixed_width_cols[1] . 'v' :
\ '')
endif
return pat . (a:zs_flag ? '\zs' : '')
endfu
fu! <sid>SetupAutoCmd(window,bufnr) "{{{3
" Setup QuitPre autocommand to quit cleanly
aug CSV_QuitPre
au!
exe "au QuitPre * call CSV_CloseBuffer(".winbufnr(a:window).")"
exe "au CursorHold <buffer=".a:bufnr."> call CSV_SetSplitOptions(".a:window.")"
aug END
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
let bufnr = bufnr('.')
if a:hor
setl scrollopt=hor scrollbind cursorbind
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
noa 1
sil! sign unplace *
exe "resize" . lines
setl scrollopt=hor winfixheight nowrap cursorbind
let &l:stl="%#Normal#".repeat(' ',winwidth(0))
let s:local_stl = &l:stl
" set the foldcolumn to the same of the other window
let &l:fdc = _fdc
else
setl scrollopt=ver scrollbind cursorbind
noa 0
if a:lines[-1:] is? '!'
let a=<sid>CopyCol('',a:lines,'')
else
let a=<sid>CopyCol('',1, a:lines-1)
endif
" Does it make sense to use the preview window?
"vert sil! pedit |wincmd w | enew!
above vsp +enew
call append(0, a)
$d _
let b:col = b
sil! doautocmd FileType csv
" remove leading delimiter
exe "sil :%s/^". b:delimiter. "//e"
" remove trailing delimiter
exe "sil :%s/". b:delimiter. "\s*$//e"
syn clear
noa 0
let b:csv_SplitWindow = winnr()
sil :call <sid>ArrangeCol(1,line('$'), 1, -1)
sil! sign unplace *
exe "vert res" . len(split(getline(1), '\zs'))
call matchadd("CSVHeaderLine", b:col)
setl scrollopt=ver winfixwidth cursorbind nonu nornu fdc=0
endif
call <sid>SetupAutoCmd(winnr(),bufnr)
" disable airline
let w:airline_disabled = 1
let win = winnr()
setl scrollbind buftype=nowrite bufhidden=wipe noswapfile nobuflisted
noa 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 nocursorbind
try
noa wincmd c
catch /^Vim\%((\a\+)\)\=:E444/ " cannot close last window
catch /^Vim\%((\a\+)\)\=:E517/ " buffer already wiped
" no-op
endtry
"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
" a:1 is there to have some mappings move in the same
" direction but still stop at a different position
" see :h csv-mapping-H
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
if foldclosed(line) != -1
let line = line > line('.') ? foldclosedend(line) + 1 : foldclosed(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')
let stime=localtime()
while getpos('.')[2] == cpos && <sid>Timeout(stime) " make sure loop terminates
" 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
if (exists("a:1") && a:1)
" H also stops at the beginning of the content
" of a field.
let epos = getpos('.')
if getline('.')[col('.')-1] == ' '
call search('\S', 'W', line('.'))
if getpos('.')[2] > spos
call setpos('.', epos)
endif
endif
endif
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>Sort(bang, line1, line2, colnr) range "{{{3
" :Sort command
let wsv = winsaveview()
let flag = matchstr(a:colnr, '[nixo]')
let col = (empty(a:colnr) || a:colnr !~? '\d\+[nixo]\?') ? <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'. flag. ' /' . pat . '/'
call winrestview(wsv)
endfun
fu! <sid>CopyCol(reg, col, cnt) "{{{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
" The number of columns to return
" by default (value of zero, will only return that specific column)
let cnt_cols = col - 1
if !empty(a:cnt) && a:cnt > 0 && col + a:cnt <= mcol
let cnt_cols = col + a:cnt - 1
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:cnt_cols]')
else
call map(a, 'matchstr(v:val, <sid>GetColPat(col, 0)).*<sid>GetColPat(col+cnt_cols, 0)')
endif
if type(a[0]) == type([])
call map(a, 'join(v:val, "")')
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
else
let pos = a:1
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>MaxColumn(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 result = []
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
call add(result, has("float") ? str2float(nr) : nr+0)
endfor
let result = sort(result, s:numeric_sort ? 'n' : 's:CSVSortValues')
let ind = len(result) > 9 ? 9 : len(result)
return s:additional.ismax ? reverse(result)[:ind] : result[:ind]
endif
endfu
fu! <sid>CountColumn(list) "{{{3
if empty(a:list)
return 0
elseif has_key(get(s:, 'additional', {}), 'distinct') && s:additional['distinct']
if exists("*uniq")
return len(uniq(sort(a:list)))
else
let l = {}
for item in a:list
let l[item] = get(l, 'item', 0) + 1
endfor
return len(keys(l))
endif
else
return len(a:list)
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)
if foldlevel(line)
" Filter out folded lines (from dynamic filter)
continue
endif
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)
let a = <sid>ProcessFieldValue(a)
let pat = '\%(^\|'.b:delimiter. '\)\@<='.<sid>EscapeValue(a).
\ '\m\ze\%('.b:delimiter.'\|$\)'
" Make a column pattern
let b= '\%(' .
\ (exists("b:csv_fixed_width") ? '.*' : '') .
\ <sid>GetPat(col, max, pat, 0) . '\)'
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
" Fold settings:
call <sid>LocalSettings('fold')
" Don't put spaces between the arguments!
exe 'setl foldexpr=<snr>' . s: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>ProcessFieldValue(field) "{{{3
let a = a:field
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
return a
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
exe 'setl foldexpr=<snr>' . s: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>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 empty(item) || item ==# b:delimiter
let item = 'NULL'
endif
if !get(res, item)
let res[item] = 0
endif
let res[item]+=1
endfor
let max_items = reverse(sort(values(res), s:numeric_sort ? 'n' : 's:CSVSortValues'))
" What about the minimum 5 items?
let count_items = keys(res)
if len(max_items) > 5
call remove(max_items, 5, -1)
call map(max_items, 'printf(''\V%s\m'', escape(v:val, ''\\''))')
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
echo printf("%s", repeat('=', strdisplaywidth(title)))
echo printf("different values: %d", len(count_items))
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(), '.*', 1)
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 max_len = len(split(getline(1), '\zs'))
let _cc = &l:cc
let &l:cc = 1
redraw!
let Dict = {'1': 1} " first column is always the start of a new column
let tcc = &l:cc
let &l:cc = 1
echo "<Cursor>, <Space>, <ESC>, <BS>, <CR>..."
let char=getchar()
while 1
if char == "\<Left>" || char == "\<Right>"
let tcc = eval('tcc'.(char=="\<Left>" ? '-' : '+').'1')
if tcc < 0
let tcc=0
elseif tcc > max_len
let tcc = max_len
endif
elseif char == "\<Space>" || char == 32 " Space
let Dict[tcc] = 1
elseif char == "\<BS>" || char == 127
try
call remove(Dict, reverse(sort(keys(Dict)))[0])
catch /^Vim\%((\a\+)\)\=:E\(\%(716\)\|\%(684\)\)/ " Dict or List empty
break
endtry
elseif char == "\<ESC>" || char == 27
let &l:cc=_cc
redraw!
return
elseif char == "\<CR>" || char == "\n" || char == "\r" " Enter
let Dict[tcc] = 1
break
else
break
endif
let &l:cc=tcc . (!empty(keys(Dict))? ',' . join(keys(Dict), ','):'')
redraw!
echo "<Cursor>, <Space>, <ESC>, <BS>, <CR>..."
let char=getchar()
endw
let b:csv_fixed_width_cols=[]
let tcc=0
let b:csv_fixed_width_cols = sort(keys(Dict), s:numeric_sort ? 'n' : 's:CSVSortValues')
let b:csv_fixed_width = join(sort(keys(Dict), s:numeric_sort ? 'n' : 's:CSVSortValues'), ',')
call <sid>Init(1, line('$'))
let &l:cc=_cc
redraw!
endfu
fu! <sid>NewRecord(line1, line2, count) "{{{3
if a:count =~ "\D"
call <sid>Warn("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 outer_field = a:outer
let cur_field = <sid>WColumn()
let _wsv = winsaveview()
if cur_field == <sid>MaxColumns()
let last = 1
if !outer_field && getline('.')[-1:] != b:delimiter
" No trailing delimiter, so inner == outer
let outer_field = 1
endif
endif
" Move 1 column backwards, unless the cursor is in the first column
" or in front of a delimiter
if matchstr(getline('.'), '.\%'.virtcol('.').'v') != b:delimiter && virtcol('.') > 1
call <sid>MoveCol(-1, line('.'))
endif
" if cur_field != <sid>WColumn()
" cursor was at the beginning of the field, and moved back to the
" previous field, move back to original position
" call cursor(_wsv.lnum, _wsv.col)
" endif
let _s = @/
if last
exe "sil! norm! v$h" . (outer_field ? "" : "h") . (&sel ==# 'exclusive' ? "l" : '')
else
exe "sil! norm! v/." . b:col . "\<cr>h" . (outer_field ? "" : "h") . (&sel ==# 'exclusive' ? "l" : '')
endif
let _wsv.col = col('.')-1
call winrestview(_wsv)
let @/ = _s
endfu
fu! <sid>CSVMappings() "{{{3
call <sid>Map('noremap', 'W', ':<C-U>call <SID>MoveCol(1, line("."))<CR>')
call <sid>Map('noremap', '<C-Right>', ':<C-U>call <SID>MoveCol(1, line("."))<CR>')
call <sid>Map('noremap', 'L', ':<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', '<C-Left>', ':<C-U>call <SID>MoveCol(-1, line("."))<CR>')
call <sid>Map('noremap', 'H', ':<C-U>call <SID>MoveCol(-1, line("."), 1)<CR>')
call <sid>Map('noremap', 'K', ':<C-U>call <SID>MoveCol(0, line(".")-v:count1)<CR>')
call <sid>Map('nnoremap', '<Up>', ':<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', '<Down>', ':<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('xnoremap', 'if', ':<C-U>call <sid>MoveOver(0)<CR>')
call <sid>Map('xnoremap', 'af', ':<C-U>call <sid>MoveOver(1)<CR>')
call <sid>Map('omap', 'af', ':norm vaf<cr>')
call <sid>Map('omap', 'if', ':norm vif<cr>')
call <sid>Map('xnoremap', 'iL', ':<C-U>call <sid>SameFieldRegion()<CR>')
call <sid>Map('omap', 'iL', ':<C-U>call <sid>SameFieldRegion()<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('xnoremap', '<LocalLeader>W', 'W')
call <sid>Map('xnoremap', '<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>')
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, -1, <q-args>)',
\ '-range -bang -nargs=?')
call <sid>LocalCmd("UnArrangeColumn",
\':call <sid>PrepUnArrangeCol(<line1>, <line2>)',
\ '-range')
call <sid>LocalCmd("InitCSV", ':call <sid>Init(<line1>,<line2>,<bang>0)',
\ '-bang -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>,<q-args>)',
\ '-count -register -nargs=?')
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("MaxCol",
\ ':echo csv#EvalColumn(<q-args>, "<sid>MaxColumn", <line1>,<line2>, 1)',
\ '-nargs=? -range=% -complete=custom,<sid>SortComplete')
call <sid>LocalCmd("MinCol",
\ ':echo csv#EvalColumn(<q-args>, "<sid>MaxColumn", <line1>,<line2>, 0)',
\ '-nargs=? -range=% -complete=custom,<sid>SortComplete')
call <sid>LocalCmd("CountCol",
\ ':echo csv#EvalColumn(<q-args>, "<sid>CountColumn", <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>, 1, line(''$''))',
\ '-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('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'
elseif a:map == 'xnoremap'
let unmap = 'xunmap'
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, firstl, lastl) "{{{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 delimiter = a:newdelimiter
if a:newdelimiter is '\t'
let delimiter="\t"
endif
let line=a:firstl
while line <= a:lastl
" 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, delimiter))
let line+=1
endwhile
" reset local buffer options
for [key, value] in items(save)
call setbufvar('', '&'. key, value)
endfor
"reinitialize the plugin
if exists("g:csv_delim")
let _delim = g:csv_delim
endif
let g:csv_delim = delimiter
call <sid>Init(1,line('$'))
if exists("_delim")
let g:csv_delim = _delim
else
unlet g:csv_delim
endif
unlet! _delim
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
if match(split(&ft, '\.'),'csv') == -1
call <sid>Warn("No CSV filetype, aborting!")
return
endif
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()
let line=a:first
if exists("g:csv_table_leftalign")
let b:csv_arrange_leftalign = 1
endif
let newlines=[]
let content=[]
while line <= a:last
if foldclosed(line) != -1
let line = foldclosedend(line) + 1
continue
endif
let curline = getline(line)
call add(content, curline)
if empty(split(curline, b:delimiter))
" only empty delimiters, add one empty delimiter
" (:NewDelimiter strips trailing delimiter
let curline = repeat(b:delimiter, <sid>MaxColumns())
call add(newlines, line)
call setline(line, curline)
endif
let line+=1
endw
unlet! line
let delim=b:delimiter
new
call setline(1,content)
let b:delimiter=delim
call <sid>Init(1,line('$'), 1)
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(1, line('$'), 0, -1)
if !get(b:, 'csv_arrange_leftalign',0)
for line in newlines
let cline = getline(line)
let cline = substitute(cline, '\s$', ' ', '')
call setline(line, cline)
endfor
unlet! line
endif
endif
if empty(b:col_width)
call <sid>Warn('An error occured, aborting!')
return
endif
if getline(a:first)[-1:] isnot? b:delimiter
let b:col_width[-1] += 1
endif
let marginline = s:td.scol. join(map(copy(b:col_width), 'repeat(s:td.hbar, v:val)'), s:td.cros). s:td.ecol
call <sid>NewDelimiter(s:td.vbar, 1, line('$'))
"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', 1, line('$'),
\ '^[^'. s:td.vbar. s:td.scol. ']', s:td.vbar.'&')
" And add a final vertical bar, if there isn't one already
exe printf('sil %d,%ds/%s/%s/e', 1, line('$'),
\ '[^'. s:td.vbar. s:td.ecol. ']$', '&'. s:td.vbar)
" Make nice intersection graphs
let line = split(getline(1), s:td.vbar)
call map(line, 'substitute(v:val, ''[^''.s:td.vbar. '']'', s:td.hbar, ''g'')')
" Set top and bottom margins
call append(0, s:td.ltop. join(line, s:td.dhor). s:td.rtop)
call append(line('$'), s:td.lbot. join(line, s:td.uhor). s:td.rbot)
if s:csv_fold_headerline > 0
call append(1 + s:csv_fold_headerline, marginline)
let adjust_last += 1
endif
" Syntax will be turned off, so disable this part
"
" Adjust headerline to header of new table
"let b:csv_headerline = (exists('b:csv_headerline')?b:csv_headerline+2:3)
"call <sid>CheckHeaderLine()
" Adjust syntax highlighting
"unlet! b:current_syntax
"ru syntax/csv.vim
if a:bang
exe printf('sil %d,%ds/^%s\zs\n/&%s&/e', 1 + s:csv_fold_headerline, line('$') + 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], 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
let mode = mode()
if mode =~# 'R'
" (virtual) Replace mode
let new_line = (line('.') == line('$') ||
\ (synIDattr(synIDtrans(synID(line("."), col("."), 1)), "name") =~? "comment"))
return "\<ESC>g`[". (new_line ? "o" : "J".mode)
else
return "\<CR>"
endif
endfu
fu! <sid>Timeout(start) "{{{3
return localtime()-a:start < 2
endfu
fu! <sid>SameFieldRegion() "{{{3
" visually select the region, that has the same value in the cursor field
let col = <sid>WColumn()
let max = <sid>MaxColumns()
let field = <sid>GetColumn(line('.'), col)
let line = line('.')
let limit = [line, line]
" Search upwards and downwards from the current position and find the
" limit of the current selection
while line > 1
let line -= 1
if <sid>GetColumn(line, col) ==# field
let limit[0] = line
else
break
endif
endw
let line = line('.')
while line > 1 && line < line('$')
let line += 1
if <sid>GetColumn(line, col) ==# field
let limit[1] = line
else
break
endif
endw
exe printf(':norm! %dGV%dG',limit[0],limit[1])
endfu
fu! CSV_CloseBuffer(buffer) "{{{3
" Setup by SetupAutoCmd autocommand
try
if bufnr((a:buffer)+0) > -1
exe a:buffer. "bw"
endif
catch /^Vim\%((\a\+)\)\=:E517/ " buffer already wiped
" no-op
finally
augroup CSV_QuitPre
au!
augroup END
augroup! CSV_QuitPre
endtry
endfu
fu! CSV_SetSplitOptions(window) "{{{3
if exists("s:local_stl")
" local horizontal statusline
for opt in items({'&nu': &l:nu, '&rnu': &l:rnu, '&fdc': &fdc})
if opt[1] != getwinvar(a:window, opt[0])
call setwinvar(a:window, opt[0], opt[1])
endif
endfor
" Check statusline (airline might change it)
if getwinvar(a:window, '&l:stl') != s:local_stl
call setwinvar(a:window, '&stl', s:local_stl)
endif
endif
endfun
" Global functions "{{{2
fu! csv#EvalColumn(nr, func, first, last, ...) range "{{{3
" Make sure, the function is called for the correct filetype.
if match(split(&ft, '\.'), 'csv') == -1
call <sid>Warn("File is no CSV file!")
return
endif
let save = winsaveview()
call <sid>CheckHeaderLine()
let nr = matchstr(a:nr, '^\-\?\d\+')
let col = (empty(nr) ? <sid>WColumn() : nr)
if col == 0
let col = 1
endif
" 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, '/[^/]*/')
let s:additional={}
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
let distinct = matchstr(a:nr, '\<distinct\>')
if !empty(distinct)
let s:additional.distinct=1
endif
if function(a:func) is# function("\<snr>".s:SID."_MaxColumn")
let s:additional.ismax = a:1
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 : '.*', 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! CSVSum(col, fmt, first, last) "{{{3
let first = a:first
let last = a:last
if empty(first)
let first = 1
endif
if empty(last)
let last = line('$')
endif
return csv#EvalColumn(a:col, '<sid>SumColumn', first, last)
endfu
fu! CSVMax(col, fmt, first, last) "{{{3
let first = a:first
let last = a:last
if empty(first)
let first = 1
endif
if empty(last)
let last = line('$')
endif
return csv#EvalColumn(a:col, '<sid>MaxColumn', first, last, 1)
endfu
fu! CSVMin(col, fmt, first, last) "{{{3
let first = a:first
let last = a:last
if empty(first)
let first = 1
endif
if empty(last)
let last = line('$')
endif
return csv#EvalColumn(a:col, '<sid>MaxColumn', first, last, 0)
endfu
fu! CSVCount(col, fmt, first, last, ...) "{{{3
let first = a:first
let last = a:last
let distinct = 0
if empty(first)
let first = 1
endif
if empty(last)
let last = line('$')
endif
if !exists('s:additional')
let s:additional = {}
endif
if exists("a:1") && !empty(a:1)
let s:additional['distinct'] = 1
endif
let result=csv#EvalColumn(a:col, '<sid>CountColumn', first, last, distinct)
unlet! s:additional['distinct']
return (empty(result) ? 0 : result)
endfu
fu! CSV_WCol(...) "{{{3
" Needed for airline
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:
doc/ft-csv.txt [[[1
1884
*ft-csv.txt* For Vim version 7.4 Last Change: Thu, 15 Jan 2015
Author: Christian Brabandt <cb@256bit.org>
Version: 0.31
Homepage: http://www.vim.org/scripts/script.php?script_id=2830
The VIM LICENSE applies to the CSV filetype plugin (see |copyright|).
NO WARRANTY, EXPRESS OR IMPLIED. USE AT-YOUR-OWN-RISK.
*csv-toc*
1. Introduction.................................|csv-intro|
2. Installation.................................|csv-installation|
3. CSV Commands.................................|csv-commands|
3.1 WhatColumn..............................|WhatColumn_CSV|
3.2 NrColumns...............................|NrColumns_CSV|
3.3 SearchInColumn..........................|SearchInColumn_CSV|
3.4 HiColumn................................|HiColumn_CSV|
3.5 ArrangeColumn...........................|ArrangeColumn_CSV|
3.6 UnArrangeColumn.........................|UnArrangeColumn_CSV|
3.7 DeleteColumn............................|DeleteColumn_CSV|
3.8 InitCSV.................................|InitCSV|
3.9 Header..................................|Header_CSV|
3.10 Sort...................................|Sort_CSV|
3.11 CopyColumn.............................|Copy_CSV|
3.12 MoveColumn.............................|MoveCol_CSV|
3.13 Sum of a column........................|SumCol_CSV|
3.14 Create new records ....................|NewRecord_CSV|
3.15 Change the delimiter...................|NewDelimiter_CSV|
3.16 Check for duplicate records............|Duplicate_CSV|
3.17 Normal mode commands...................|csv-mapping|
3.18 Convert CSV file.......................|csv-convert|
3.19 Dynamic filters........................|csv-filter|
3.20 Analyze a column.......................|csv-analyze|
3.21 Vertical Folding.......................|csv-vertfold|
3.22 Transposing columns....................|csv-transpose|
3.23 Transforming into a table..............|csv-tabularize|
3.24 Add new empty columns..................|AddColumn_CSV|
3.25 Substitute in columns..................|Substitute_CSV|
3.26 Count values inside a column...........|Count_CSV|
3.27 Maximum/Minimum values ................|MaxCol_CSV|
4. CSV Filetype configuration...................|csv-configuration|
4.1 Delimiter...............................|csv-delimiter|
4.2 Column..................................|csv-column|
4.3 HiGroup.................................|csv-higroup|
4.4 Strict Columns..........................|csv-strict|
4.5 Concealing..............................|csv-conceal|
4.6 Newlines................................|csv-newline|
4.7 Highlight column automatically..........|csv-hicol|
4.8 Fixed width columns.....................|csv-fixedwidth|
4.8.1 Manual setup
4.8.2 Setup using a Wizard
4.9 CSV Header lines........................|csv-header|
4.10 Number format..........................|csv-nrformat|
4.11 Move folded lines......................|csv-move-folds|
4.12 Using Comments.........................|csv-comments|
5. Functions....................................|CSV-Functions|
5.1 CSVPat()................................|CSVPat()|
5.2 CSVField()..............................|CSVField()|
5.3 CSVCol()................................|CSVCol()|
5.4 CSVCount()..............................|CSVCount()|
5.4 CSVMax()................................|CSVMax()|
5.4 CSVMin()................................|CSVMin()|
6. CSV Tips and Tricks..........................|csv-tips|
6.1 Statusline..............................|csv-stl|
6.2 Slow CSV plugin.........................|csv-slow|
6.3 Defining custom aggregate functions.....|csv-aggregate-functions|
6.4 Autocommand on opening/closing files....|csv-arrange-autocmd|
6.5 CSV Syntax error........................|csv-syntax-error|
6.6 Calculating new column values...........|csv-calculate-column|
7. CSV Changelog................................|csv-changelog|
==============================================================================
1. Introduction *csv-intro*
This plugin is used for handling column separated data with Vim. Usually those
files are called csv files and use the ',' as delimiter, though sometimes they
use e.g. the '|' or ';' as delimiter and there also exists fixedwidth columns.
The aim of this plugin is to ease handling these kinds of files.
This is a filetype plugin for CSV files. It was heavily influenced by
the Vim Wiki Tip667 (http://vim.wikia.com/wiki/VimTip667), though it
works differently. For instructions on installing this file, type
:help add-local-help |add-local-help| inside Vim. For a screenshot, of
how the plugin can be used, see http://www.256bit.org/~chrisbra/csv.gif
==============================================================================
2. Installation *csv-installation*
In order to have vim automatically detect csv files, you need to have
|ftplugins| enabled (e.g. by having this line in your |.vimrc| file: >
:filetype plugin on
<
The plugin already sets up some logic to detect CSV files. By default,
the plugin recognizes *.csv and *.dat files as CSV filetype. In order that the
CSV filetype plugin is loaded correctly, vim needs to be enabled to load
|filetype-plugins|. This can be ensured by putting a line like this in your
|.vimrc|: >
:filetype plugin on
<
(see also |filetype-plugin-on|).
In case this did not work, you need to setup vim like this:
To have Vim automatically detect csv files, you need to do the following.
1) Create your user runtime directory if you do not have one yet. This
directory needs to be in your 'runtime' path. In Unix this would
typically the ~/.vim directory, while in Windows this is usually your
~/vimfiles directory. Use :echo expand("~") to find out, what Vim thinks
your user directory is.
To create this directory, you can do: >
:!mkdir ~/.vim
<
for Unix and >
:!mkdir ~/vimfiles
<
for Windows.
2) In that directory you create a file that will detect csv files. >
if exists("did_load_csvfiletype")
finish
endif
let did_load_csvfiletype=1
augroup filetypedetect
au! BufRead,BufNewFile *.csv,*.dat setfiletype csv
augroup END
<
You save this file as "filetype.vim" in your user runtime diretory: >
:w ~/.vim/filetype.vim
<
3) To be able to use your new filetype.vim detection, you need to restart
Vim. Vim will then load the csv filetype plugin for all files whose
names end with .csv.
==============================================================================
3. Commands *csv-commands*
The CSV ftplugin provides several Commands. All commands are also provided
with the prefix :CSV (e.g. |:CSVNrColumns|)
*:CSVWhatColumn*
3.1 WhatColumn *WhatColumn_CSV*
--------------
If you would like to know, on which column the cursor is, use >
:WhatColumn
<
or >
:CSVWhatColumn
<
Use the bang attribute, if you have a heading in the first line and you want
to know the name of the column in which the cursor is: >
:WhatColumn!
<
*:CSVNrColumns*
3.2 NrColumns *NrColumns_CSV*
--------------
`:NrColumns` and `:CSVNrColumns` outputs the maximum number of columns
available. It does this by testing the first 10 lines for the number of
columns. This usually should be enough. If you use the '!' attribute, it
outputs the number of columns in the current line.
*:CSVSearchInColumn*
3.3 SearchInColumn *SearchInColumn_CSV*
------------------
Use `:SearchInColumn` or `:CSVSearchInColumn` to search for a pattern within a
specific column. The usage is: >
:SearchInColumn [<nr>] /{pat}/
<
So if you would like to search in Column 1 for the word foobar, you enter >
:SearchInColumn 1 /foobar/
Instead of / as delimiter, you can use any other delimiter you like. If you
don't enter a column, the current column will be used.
*:CSVHiColumn*
3.4 HiColumn *HiColumn_CSV*
------------
`:HiColumn` or `:CSVHiColumn` <nr> can be used to highlight Column <nr>.
Currently the plugin uses the WildMenu Highlight Group. If you would like to
change this, you need to define the variable |g:csv_hiGroup|.
If you do not specify a <nr>, HiColumn will highlight the column on which the
cursor is. Use >
:HiColumn!
to remove any highlighting.
If you want to automatically highlight a column, see |csv-hicol|
*:ArrangeColumn* *:CSVArrangeColumn*
3.5 ArrangeColumn *ArrangeColumn_CSV*
-----------------
If you would like all columns to be visually arranged, you can use the
`:ArrangeColumn` or `:CSVArrangeColumn` command: >
:[range]ArrangeColumn[!] [<Row>]
Beware, that this will change your file and depending on the size of
your file may slow down Vim significantly. This is highly experimental.
:ArrangeCommand will try to vertically align all columns by their maximum
column size. While the command is run, a progressbar in the statusline 'stl'
will be shown.
Use the bang attribute to force recalculating the column width. This is
slower, but especially if you have modified the file, this will correctly
calculate the width of each column so that they can be correctly aligned. If
no column width has been calculated before, the width will be calculated, even
if the '!' has not been given.
If <Row> is given, will use the Row, to calculate the width, else will
calculate the maximum of at least the first 10,000 rows to calculate the
width. The limit of 10,000 is set to speed up the processing and can be
overriden by setting the "b:csv_arrange_use_all_rows" variable (see below).
If [range] is not given, it defaults to the current line.
By default, the columns will be righ-aligned. If you want a different
alignment you need to specify this through the b:csv_arrange_align variable.
This is a string of flags ('r': right align, 'l': left align, 'c': center
alignment, '.': decimal alignment) where each flag defines the alignment for
a particular column (starting from left). Missing columns will be right aligned.
So this: >
:let b:csv_arrange_align = 'lc.'
<
Will left-align the first column, center align the second column, decimal
align the third column and all following columns right align. (Note: decimal
aligning might slow down Vim and additionally, if the value is no decimal
number it will be right aligned).
Note, arranging the columns can be very slow on large files or many columns (see
|csv-slow| on how to increase performance for this command). For large files,
calculating the column width can take long and take a consierable amount of
memory. Therefore, the csv plugin will at most check 10.000 lines for the
width. Set the variable b:csv_arrange_use_all_rows to 1 to use all records: >
:let b:csv_arrange_use_all_rows = 1
<
(this could however in the worst case lead to a crash).
To disable the statusline progressbar set the variable g:csv_no_progress: >
:let g:csv_no_progress = 1
<
This will disable the progressbar and slightly improve performance (since no
additional redraws are needed).
Note: this command does not work for fixed width columns |csv-fixedwidth|
See also |csv-arrange-autocmd| on how to have vim automaticaly arrange a CSV
file upon entering it.
*:CSVUnArrangeColumn*
3.6 UnArrangeColumn *UnArrangeColumn_CSV*
-----------------
If you would like to undo a previous :ArrangeColumn command, you can use this
`:UnArrangeColumn` or `:CSVUnArrangeColumn` command: >
:[range]UnArrangeColumn
Beware, that is no exact undo of the :ArrangeColumn command, since it strips
away all leading blanks for each column. So if previously a column contained
only some blanks, this command will strip all blanks.
If [range] is given, it defaults to the current line.
*:CSVDeleteColumn*
3.7 DeleteColumn *DeleteColumn_CSV*
----------------
The command `:DeleteColumn` or `:CSVDeleteColumn` can be used to delete a specific column. >
:DeleteColumn 2
will delete column 2.
If you don't specify a column number, it will delete the column on which the
cursor is. Alternatively, you can also specify a search string. The plugin
will then delete all columns that match the pattern: >
:DeleteColumn /foobar
<
will delete all columns where the pattern "foobar" matches.
*:CSVInitCSV*
3.8 InitCSV *InitCSV*
-----------
Reinitialize the Plugin. Use this, if you have changed the configuration
of the plugin (see |csv-configuration| ).
If you use the bang (!) attribute, it will keep the b:delimiter configuration
variable.
*:CSVHeader*
3.9 Header lines *Header_CSV*
----------------
The `:Header` or `:CSVHeader` command splits the csv-buffer and adds a window,
that holds a small fraction of the csv file. This is useful, if the first line
contains some kind of a heading and you want always to display it. This works
similar to fixing a certain line at the top. As optional argument, you can
give the number of columns from the top, that shall be displayed. By default,
1 is used (You can define youre own default by setting the b:csv_headerline
variable, see |csv-header|). Use the '!' to close this window. So this >
:Header 3
opens at the top a split window, that holds the first 3 lines, is fixed
and horizontally 'scrollbind'ed to the csv window and highlighted using the
CSVHeaderLine highlighting.
To close the header window, use >
:Header!
Note, this won't work with linebreaks in the column.
Note also, that if you already have a horizontal header window (|VHeader_CSV|),
this command will close the horizontal Header window. This is because of a
limitation of Vim itsself, which doesn't allow to sync the scrolling between
two windows horizontally and at the same time have another window only sync
its scrolling vertically.
Note: this command does not work for fixed width columns |csv-fixedwidth|
*:CSVVHeader* *VHeader_CSV*
If you want a vertical header line, use `:VHeader` or `:CSVVHeader`. This works
similar to the |Header_CSV| command, except that it will open a vertical split
window with the first column always visible. It will always open the first
column in the new split window. Use the '!' to close the window. If you
specify a count, that many columns will be visible (default: the first). Add
the bang to the count, if you only want the specific column to be visible.
>
:VHeader 2
<
This will open a vertical split window containing the first 2 columns, while
>
:VHeader 2!
<
Opens a new vertical split window containing only the 2 second column.
Note, this won't work with linebreaks in the column.
Note also: this command does not work for fixed width columns |csv-fixedwidth|
*:CSVVHeaderToggle* *:CSVHeaderToggle*
*VHeaderToggle_CSV* *HeaderToggle_CSV*
Use the `:HeaderToggle` and `:VHeaderToggle` command to toggle displaying the
horizontal or vertical header line. Alternatively, use `:CSVHeaderToggle` or
`:CSVVHeaderToggle`
*:CSVSort*
3.10 Sort *Sort_CSV*
---------
The command `:Sort` or `:CSVSort` can be used to sort the csv file on a
certain column. If no range is given, is sorts the whole file. Specify the
column number to sort on as argument. Use the '!' attribute to reverse the
sort order. For example, the following command sorts line 1 til 10 on the 3
column >
:1,10Sort 3
While this command >
:1,10Sort! 3
reverses the order based on column 3.
The column number can be optionally followed by any of the flags [i], [n],
[x] and [o] for [i]gnoring case, sorting by [n]umeric, he[x]adecimal
or [o]ctal value.
When no column number is given, it will sort by the column, on which the
cursor is currently.
*:CSVColumn*
3.11 Copy Column *Copy_CSV*
----------------
If you need to copy a specific column, you can use the command `:CSVColumn` or
`:Column` >
:[N]Column [a]
Copy column N into register a. This will copy all the values, that are
not folded-away (|csv-filter|) and skip comments.
If you don't specify N, the column of the current cursor position is used.
If no register is given, the default register
|quotequote| is used.
*:CSVMoveCol*
3.12 Move A Column *MoveCol_CSV*
------------------
You can move one column to the right of another column by using the
`:CSVMoveColumn` or `:MoveColumn` command >
:[range]MoveColumn [source] [dest]
This moves the column number source to the right of column nr destination. If
both arguments are not given, move the column on which the cursor is to the
right of the current last column. If [range] is not given, MoveColumn moves
the entire column, otherwise, it moves the columns only for the lines within
the range, e.g. given that your first line is a header line, which you don't
want to change >
:2,$MoveColumn 1 $
this would move column 1 behind the last column, while keeping the header line
as is.
*:CSVSumCol*
3.13 Sum of a Column *SumCol_CSV*
--------------------
You can let Vim output the sum of a column using the `:CSVSumCol` or `:SumCol`
command >
:[range]SumCol [nr] [/format/]
This outputs the result of the column <nr> within the range given. If no range
is given, this will calculate the sum of the whole column. If <nr> is not
given, this calculates the sum for the column the cursor is on. Note, that the
delimiter will be stripped away from each value and also empty values won't be
considered.
By default, Vim uses the a numerical format that uses the '.' as decimal
separator while there is no thousands separator. If youre file contains
the numbers in a different format, you can use the /format/ option to specify
a different thousands separator or a different decimal separator. The format
needs to be specified like this:
/x:y/
where 'x' defines the thousands separator and y defines the decimal
separator and each one is optional. This means, that >
:SumCol 1 /:,/
uses the default thousands separator and ',' as the decimal separator and >
:SumCol 2 / :./
uses the Space as thousands separator and the '.' as decimal separator.
Note, if you Vim is compiled without floating point number format (|+float|),
Vim will only aggregate the integer part and therefore won't use the 'y'
argument in the /format/ specifier.
See also |csv-aggregate-functions|
*:CSVNewRecord*
3.14 Create new Records *NewRecord_CSV*
-----------------------
If you want to create one or several records, you can use the `:NewRecord` or
`:CSVNewRecord` command: >
:[range]NewRecord [count]
This will create in each line given by range [count] number of new empty
records. If [range] is not specified, creates a new line below the line the
cursor is on and if count is not given, it defaults to 1.
*:CSVNewDelimiter*
3.15 Change the delimiter *NewDelimiter_CSV*
-------------------------
If you want to change the field delimiter of your file you can use the
`:CSVNewDelimiter` or `:NewDelimiter` command: >
:NewDelimiter char
This changes the field delimiter of your file to the new delimiter "char".
Note: Will remove trailing delimiters.
*:CSVDuplicate*
3.16 Check for duplicate records *Duplicate_CSV*
--------------------------------
If you want to check the file for duplicate records, use the command
`:Duplicate` or `:CSVDuplicate`: >
:Duplicate columnlist
<
Columnlist needs to be a numeric comma-separated list of all columns that you
want to check. You can also use a range like '2-5' which means the plugin
should check columns 2,3,4 and 5.
If the plugin finds a duplicate records, it outputs its line number (but it