" Vimball Archiver by Charles E. Campbell, Jr., Ph.D. UseVimball finish ftplugin/csv.vim [[[1 2572 " Filetype plugin for editing CSV files. "{{{1 " Author: Christian Brabandt " 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! DetermineSID() let s:SID = matchstr(expand(''), '\zs\d\+\ze_DetermineSID$') endfu call s:DetermineSID() delf s:DetermineSID fu! CSVArrangeCol(first, last, bang, limit) range "{{{2 if &ft =~? 'csv' call 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! Warn(mess) "{{{3 echohl WarningMsg echomsg "CSV: " . a:mess echohl Normal endfu fu! Init(startline, endline) "{{{3 " Hilight Group for Columns if exists("g:csv_hiGroup") let s:hiGroup = g:csv_hiGroup else let s:hiGroup="WildMenu" endif if !exists("g:csv_hiHeader") let s:hiHeader = "Title" else let s:hiHeader = g:csv_hiHeader endif exe "hi link CSVHeaderLine" s:hiHeader " Determine default Delimiter if !exists("g:csv_delim") let b:delimiter=GetDelimiter(a:startline, a:endline) else let b:delimiter=g:csv_delim endif " Define custom commentstring if !exists("g:csv_comment") let b:csv_cmt = split(&cms, '%s') else let b:csv_cmt = split(g:csv_comment, '%s') endif if empty(b:delimiter) && !exists("b:csv_fixed_width") call 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 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 LocalSettings('all') " define buffer-local commands call CommandDefinitions() " Check Header line " Defines which line is considered to be a header line call CheckHeaderLine() " CSV specific mappings call CSVMappings() " force reloading CSV Syntax Highlighting if exists("b:current_syntax") unlet b:current_syntax " Force reloading syntax file endif call DoAutoCommands() " enable CSV Menu call Menu(1) call 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. " " let b:undo_ftplugin .= "| delf Warn | delf Init | " \ delf GetPat | delf SearchColumn | delf DelColumn | " \ delf HiCol | delf GetDelimiter | delf WColumn | " \ delf MaxColumns | delf ColWidth | delf ArCol | " \ delf PrepUnArCol | delf UnArCol | " \ delf CalculateColumnWidth | delf Columnize | " \ delf GetColPat | delf SplitHeaderLine | " \ delf SplitHeaderToggle | delf MoveCol | " \ delf SortComplete | delf SortList | delf Sort | " \ delf CSV_WCol | delf CopyCol | delf MoveColumn | " \ delf SumColumn csv#EvalColumn | delf DoForEachColumn | " \ delf PrepareDoForEachColumn | delf CSVMappings | " \ delf Map | delf EscapeValue | delf FoldValue | " \ delf PrepareFolding | delf OutputFilters | " \ delf SortFilter | delf GetColumn | " \ delf RemoveLastItem | delf DisableFolding | " \ delf CheckHeaderLine | " \ delf AnalyzeColumn | delf Vertfold | " \ delf InitCSVFixedWidth | delf LocalCmd | " \ delf CommandDefinitions | delf NumberFormat | " \ delf NewRecord | delf MoveOver | delf Menu | " \ delf NewDelimiter | delf DuplicateRows | delf IN | " \ delf SaveOptions | delf CheckDuplicates | " \ delf CompleteColumnNr | delf CSVPat | delf Transpose | " \ delf LocalSettings() | delf AddColumn | delf SubstituteInColumn " \ delf SetupQuitPre() | delf CSV_CloseBuffer endfu fu! LocalSettings(type) "{{{3 if a:type == 'all' " CSV local settings setl nostartofline tw=0 nowrap " undo when setting a new filetype let b:undo_ftplugin = "setlocal sol& tw< wrap<" " Set browsefilter if (v:version > 703 || (v:version == 703 && has("patch593"))) \ && exists("browsefilter") let b:browsefilter="CSV Files (*.csv, *.dat)\t*.csv;*.dat\n". \ "All Files\t*.*\n" endif if has("conceal") setl cole=2 cocu=nc let b:undo_ftplugin .= '| setl cole< cocu< ' endif elseif a:type == 'fold' let s:fdt = &l:fdt let s:fcs = &l:fcs if a:type == 'fold' " Be sure to also fold away single screen lines setl fen fdm=expr setl fdl=0 fml=0 fdc=2 if !get(g:, 'csv_disable_fdt',0) let &l:foldtext=strlen(v:folddashes) . ' lines hidden' let &fcs=substitute(&fcs, 'fold:.,', '', '') if !exists("b:csv_did_foldsettings") let b:undo_ftplugin .= printf("|set fdt<|setl fcs=%s", escape(s:fcs, '\\| ')) endif endif if !exists("b:csv_did_foldsettings") let b:undo_ftplugin .= \ "| setl fen< fdm< fdl< fdc< fml< fde<" let b:csv_did_foldsettings = 1 let b:undo_ftplugin .= "| unlet! b:csv_did_foldsettings" endif endif endif endfu fu! 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 HiColumn aug end " Set highlighting for column, on which the cursor is currently HiColumn elseif exists("#CSV_HI#CursorMoved") aug CSV_HI au! CursorMoved aug end aug! CSV_HI " Remove any existing highlighting HiColumn! endif " undo autocommand: let b:undo_ftplugin .= '| exe "sil! au! CSV_HI CursorMoved "' 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 Menu(&ft=='csv') au BufEnter call Menu(1) " enable au BufLeave call Menu(0) " disable au BufNewFile,BufNew * call Menu(0) augroup END endif endfu fu! GetPat(colnr, maxcolnr, pat) "{{{3 if a:colnr > 1 && a:colnr < a:maxcolnr if !exists("b:csv_fixed_width_cols") return '^' . GetColPat(a:colnr-1,0) . '\%([^' . \ b:delimiter . ']\{-}\)\?\zs' . a:pat . '\ze' . \ '\%([^' . b:delimiter .']\{-}\)\?' . \ b:delimiter . 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 '^' . GetColPat(a:colnr - 1,0) . \ '\%([^' . b:delimiter . \ ']\{-}\)\?\zs' . a:pat . '\ze' else return '\%' . b:csv_fixed_width_cols[-1] . \ 'c\zs' . a:pat . '\ze' endif else " colnr = 1 if !exists("b:csv_fixed_width_cols") return '^' . '\%([^' . b:delimiter . ']\{-}\)\?\zs' . a:pat . \ '\ze\%([^' . b:delimiter . ']*\)\?' . b:delimiter . \ GetColPat(a:maxcolnr -1 , 0) . '$' else return a:pat . '\ze.\{-}\%' . b:csv_fixed_width_cols[1] . 'c' endif endif return '' endfu fu! SearchColumn(arg) "{{{3 try let arglist=split(a:arg) if len(arglist) == 1 let colnr=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 = 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 Warn("Error! Usage :SearchInColumn [] /pattern/") return 1 endtry let maxcolnr = MaxColumns() if colnr > maxcolnr call Warn("There exists no column " . colnr) return 1 endif let @/ = GetPat(colnr, maxcolnr, '\%('.pat. '\)') try norm! n catch /^Vim\%((\a\+)\)\=:E486/ " Pattern not found echohl Error echomsg "E486: Pattern not found in column " . colnr . ": " . pat if &vbs > 0 echomsg substitute(v:exception, '^[^:]*:', '','') endif echohl Normal endtry endfu fu! 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 DelColumn('') let i+=1 endw else let i = 1 sil call DelColumn(a:arg) endif if i > 1 call Warn(printf("%d columns deleted", i)) else call Warn("1 column deleted") endif call winrestview(_wsv) endfu fu! DelColumn(colnr) "{{{3 let maxcolnr = MaxColumns() let _p = getpos('.') if empty(a:colnr) let colnr=WColumn() else let colnr=a:colnr endif if colnr > maxcolnr call Warn("There exists no column " . colnr) return endif if colnr != '1' if !exists("b:csv_fixed_width_cols") let pat= '^' . GetColPat(colnr-1,1) . b:col else let pat= GetColPat(colnr,0) endif else " distinction between csv and fixed width does not matter here let pat= '^' . 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! HiCol(colnr, bang) "{{{3 if a:colnr > MaxColumns() && !a:bang call Warn("There exists no column " . a:colnr) return endif if !a:bang if empty(a:colnr) let colnr=WColumn() else let colnr=a:colnr endif if colnr==1 let pat='^'. GetColPat(colnr,0) elseif !exists("b:csv_fixed_width_cols") let pat='^'. GetColPat(colnr-1,1) . b:col else let pat=GetColPat(colnr,0) endif endif if exists("*matchadd") if exists("s:matchid") " ignore errors, that come from already deleted matches sil! call matchdelete(s:matchid) endif " Additionally, filter all matches, that could have been used earlier let matchlist=getmatches() call filter(matchlist, 'v:val["group"] !~ s:hiGroup') call setmatches(matchlist) if a:bang return endif let s:matchid=matchadd(s:hiGroup, pat, 0) elseif !a:bang exe ":2match " . s:hiGroup . ' /' . pat . '/' endif endfu fu! 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! 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 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, "SortList") if temp >= i let ret = j endif let j += 1 endfor endif call setpos('.',_cur) return ret endfu fu! 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! ColWidth(colnr) "{{{3 " Return the width of a column " Internal function let width=20 "Fallback (wild guess) let tlist=[] if !exists("b:csv_fixed_width_cols") if !exists("b:csv_list") let b:csv_list=getline(1,'$') let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\') call filter(b:csv_list, 'v:val !~ pat') call filter(b:csv_list, '!empty(v:val)') call map(b:csv_list, 'split(v:val, b:col.''\zs'')') endif try for item in b:csv_list call add(tlist, item[a:colnr-1]) endfor " we have a list of the first 10 rows " Now transform it to a list of field a:colnr " and then return the maximum strlen " That could be done in 1 line, but that would look ugly "call map(list, 'split(v:val, b:col."\\zs")[a:colnr-1]') call map(tlist, 'substitute(v:val, ".", "x", "g")') call map(tlist, 'strlen(v:val)') return max(tlist) catch throw "ColWidth-error" return width endtry else let cols = len(b:csv_fixed_width_cols) if a:colnr == cols return strlen(substitute(getline('$'), '.', 'x', 'g')) - \ b:csv_fixed_width_cols[cols-1] + 1 elseif a:colnr < cols && a:colnr > 0 return b:csv_fixed_width_cols[a:colnr] - \ b:csv_fixed_width_cols[(a:colnr - 1)] else throw "ColWidth-error" return 0 endif endif endfu fu! ArrangeCol(first, last, bang, limit) range "{{{3 " explicitly give the range as argument to the function if exists("b:csv_fixed_width_cols") " Nothing to do call Warn("ArrangeColumn does not work with fixed width column!") return endif let cur=winsaveview() if a:bang if a:bang " Force recalculating the Column width unlet! b:csv_list b:col_width endif elseif a:limit > -1 && a:limit < getfsize(fnamemodify(bufname(''), ':p')) return endif if !exists("b:col_width") " Force recalculation of Column width call CalculateColumnWidth() endif if &ro " Just in case, to prevent the Warning " Warning: W10: Changing read-only file let ro = 1 setl noro else let ro = 0 endif exe "sil". a:first . ',' . a:last .'s/' . (b:col) . \ '/\=Columnize(submatch(0))/' . (&gd ? '' : 'g') " Clean up variables, that were only needed for Columnize() function unlet! s:columnize_count s:max_cols s:prev_line if ro setl ro unlet ro endif call winrestview(cur) endfu fu! 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 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) . \ '/\=UnArrangeCol(submatch(0))/' . (&gd ? '' : 'g') " Clean up variables, that were only needed for Columnize() function call winrestview(cur) endfu fu! 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 " only strip leading white space, if a non-white space follows: "return substitute(a:match, '^\s\+\ze\S', '', '') endfu fu! CalculateColumnWidth() "{{{3 " Internal function, not called from external, " does not work with fixed width columns let b:col_width=[] try let s:max_cols=MaxColumns(line('.')) for i in range(1,s:max_cols) call add(b:col_width, ColWidth(i)) endfor catch /csv:no_col/ call Warn("Error: getting Column numbers, aborting!") catch /ColWidth/ call Warn("Error: getting Column Width, using default!") endtry " delete buffer content in variable b:csv_list, " this was only necessary for calculating the max width unlet! b:csv_list endfu fu! Columnize(field) "{{{3 " Internal function, not called from external, " does not work with fixed width columns if !exists("s:columnize_count") let s:columnize_count = 0 endif if !exists("s:max_cols") let s:max_cols = len(b:col_width) endif if exists("s:prev_line") && s:prev_line != line('.') let s:columnize_count = 0 endif let s:prev_line = line('.') " convert zero based indexed list to 1 based indexed list, " Default: 20 width, in case that column width isn't defined " Careful: Keep this fast! Using " let width=get(b:col_width,WColumn()-1,20) " is too slow, so we are using: let width=get(b:col_width, (s:columnize_count % s:max_cols), 20) let s:columnize_count += 1 let has_delimiter = (a:field =~# b:delimiter.'$') if v:version > 703 || v:version == 703 && has("patch713") " printf knows about %S (e.g. can handle char length if get(b:, 'csv_arrange_leftalign',0) " left-align content return printf("%-*S%s", width+1 , \ (has_delimiter ? \ matchstr(a:field, '.*\%('.b:delimiter.'\)\@=') : a:field), \ (has_delimiter ? b:delimiter : '')) else return printf("%*S", width+1 , a:field) endif else " printf only handles bytes if !exists("g:csv_no_multibyte") && \ match(a:field, '[^ -~]') != -1 " match characters outside the ascii range let a = split(a:field, '\zs') let add = eval(join(map(a, 'len(v:val)'), '+')) let add -= len(a) else let add = 0 endif " Add one for the frame " plus additional width for multibyte chars, " since printf(%*s..) uses byte width! let width = width + add + 1 if width == strlen(a:field) " Column has correct length, don't use printf() return a:field else if get(b:, 'csv_arrange_leftalign',0) " left-align content return printf("%-*s%s", width, \ (has_delimiter ? matchstr(a:field, '.*\%('.b:delimiter.'\)\@=') : a:field), \ (has_delimiter ? b:delimiter : '')) else return printf("%*s", width , a:field) endif endif endif endfun fu! 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! SetupQuitPre(window) "{{{3 " Setup QuitPre autocommand to quit cleanly if exists("##QuitPre") augroup CSV_QuitPre au! exe "au QuitPre * call CSV_CloseBuffer(".winbufnr(a:window).")" augroup end endif endfu fu! SplitHeaderLine(lines, bang, hor) "{{{3 if exists("b:csv_fixed_width_cols") call Warn("Header does not work with fixed width column!") return endif " Check that there exists a header line call CheckHeaderLine() if !a:bang " A Split Header Window already exists, " first close the already existing Window if exists("b:csv_SplitWindow") call SplitHeaderLine(a:lines, 1, a:hor) endif " Split Window let _stl = &l:stl let _sbo = &sbo let a = [] let b=b:col if a:hor setl scrollopt=hor scrollbind let _fdc = &l:fdc let lines = empty(a:lines) ? s:csv_fold_headerline : a:lines let a = getline(1,lines) " Does it make sense to use the preview window? " sil! pedit % above sp +enew call setline(1, a) " Needed for syntax highlighting "let b:col=b "setl syntax=csv sil! doautocmd FileType csv noa 1 exe "resize" . lines setl scrollopt=hor winfixheight nowrap "let &l:stl=repeat(' ', winwidth(0)) let &l:stl="%#Normal#".repeat(' ',winwidth(0)) " set the foldcolumn to the same of the other window let &l:fdc = _fdc else setl scrollopt=ver scrollbind noa 0 let a=CopyCol('',1,a:lines) " 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 ArrangeCol(1,line('$'), 1, -1) exe "vert res" . len(split(getline(1), '\zs')) call matchadd("CSVHeaderLine", b:col) setl scrollopt=ver winfixwidth endif call SetupQuitPre(winnr()) 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 call 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 try 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! SplitHeaderToggle(hor) "{{{3 if !exists("b:csv_SplitWindow") :call SplitHeaderLine(1,0,a:hor) else :call SplitHeaderLine(1,1,a:hor) endif endfu " TODO: from here on add logic for fixed-width csv files! fu! 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=WColumn() let maxcol=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) : foldclosed(line) endif " Generate search pattern if colnr == 1 let pat = '^' . 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=GetColPat(1, 0) else " Move backwards let pat=GetColPat(maxcol, 0) endif endif else if !exists("b:csv_fixed_width_cols") let pat='^'. GetColPat(colnr-1,1) . b:col else let pat=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 && Timeout(stime) " make sure loop terminates " cursor didn't move, move cursor one cell to the left norm! h if colnr > 0 call 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! SortComplete(A,L,P) "{{{3 return join(range(1,MaxColumns()),"\n") endfun fu! SortList(a1, a2) "{{{3 return a:a1+0 == a:a2+0 ? 0 : a:a1+0 > a:a2+0 ? 1 : -1 endfu fu! Sort(bang, line1, line2, colnr) range "{{{3 let wsv=winsaveview() if a:colnr =~? 'n' let numeric = 1 else let numeric = 0 endif let col = (empty(a:colnr) || a:colnr !~? '\d\+') ? WColumn() : a:colnr+0 if col != 1 if !exists("b:csv_fixed_width_cols") let pat= '^' . GetColPat(col-1,1) . b:col else let pat= GetColPat(col,0) endif else let pat= '^' . GetColPat(col,0) endif exe a:line1. ','. a:line2. "sort". (a:bang ? '!' : '') . \' r ' . (numeric ? 'n' : '') . ' /' . pat . '/' call winrestview(wsv) endfun fu! CopyCol(reg, col, cnt) "{{{3 " Return Specified Column into register reg let col = a:col == "0" ? WColumn() : a:col+0 let mcol = 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, GetColPat(col, 0)).*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! 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 = WColumn() let max = 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 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, 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! 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 Warn("Adding Columns only works for delimited files") return endif let wsv = winsaveview() let col = WColumn() let max = 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 = 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! 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! 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 Warn("You need to define how to convert your data using" . \ "the g:csv_convert variable, see :h csv-convert") return endif if exists("g:csv_pre_convert") && !empty(g:csv_pre_convert) call add(result, g:csv_pre_convert) endif for item in range(a:start, a:stop, 1) let t = g:csv_convert let line = getline(item) if line =~ '^\s*\V'. escape(b:csv_cmt[0], '\\') " Filter comments out call add(result, line) continue endif let context = split(g:csv_convert, '%s') let columns = len(context) if columns > MaxColumns() let columns = MaxColumns() elseif columns == 1 call 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, 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! 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 DoForEachColumn(a:start, a:stop, a:bang) endfun fu! EscapeValue(val) "{{{3 return '\V' . escape(a:val, '\') endfu fu! FoldValue(lnum, filter) "{{{3 call 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! 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 RemoveLastItem(s:filter_count) let s:filter_count -= 1 if empty(b:csv_filter) call DisableFolding() return endif else " Disable folding, if no pattern available call DisableFolding() return endif else let col = WColumn() let max = MaxColumns() let a = GetColumn(line('.'), col) if !exists("b:csv_fixed_width") try " strip leading whitespace if (a =~ '\s\+'. b:delimiter . '$') let b = split(a, '^\s\+\ze[^' . b:delimiter. ']\+')[0] else let b = a endif catch /^Vim\%((\a\+)\)\=:E684/ " empty pattern - should match only empty columns let b = a endtry " strip trailing delimiter try let a = split(b, b:delimiter . '$')[0] catch /^Vim\%((\a\+)\)\=:E684/ let a = b endtry if a == b:delimiter try let a=repeat(' ', ColWidth(col)) catch " no-op endtry endif endif " Make a column pattern let b= '\%(' . \ (exists("b:csv_fixed_width") ? '.*' : '') . \ GetPat(col, max, EscapeValue(a) . '\m') . \ '\)' let s:filter_count += 1 let b:csv_filter[s:filter_count] = { 'pat': b, 'id': s:filter_count, \ 'col': col, 'orig': a, 'match': a:match} endif " Put the pattern into the search register, so they will also " be highlighted " let @/ = '' " for val in sort(values(b:csv_filter), 'SortFilter') " let @/ .= val.pat . (val.id == s:filter_count ? '' : '\&') " endfor " Fold settings: call LocalSettings('fold') " Don't put spaces between the arguments! exe 'setl foldexpr=' . 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! OutputFilters(bang) "{{{3 if !a:bang call 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, "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(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 Warn("No filters defined currently!") return else exe 'setl foldexpr=' . s:SID . '_FoldValue(v:lnum,b:csv_filter)' endif endif endfu fu! SortFilter(a, b) "{{{3 return a:a.id == a:b.id ? 0 : \ a:a.id > a:b.id ? 1 : -1 endfu fu! 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, GetColPat(a:col, 0)) endif return substitute(a, '^\s\+\|\s\+$', '', 'g') endfu fu! 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! 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! 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! 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! AnalyzeColumn(...) "{{{3 let maxcolnr = MaxColumns() if len(a:000) == 1 let colnr = a:1 else let colnr = WColumn() endif if colnr > maxcolnr call Warn("There exists no column " . colnr) return 1 endif " Initialize s:fold_headerline call CheckHeaderLine() let data = 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))) let count_items = keys(res) if len(max_items) > 5 call remove(max_items, 5, -1) call filter(res, 'v:val =~ ''^''.join(max_items, ''\|'').''$''') endif if has("float") let title="Nr\tCount\t % \tValue" else let title="Nr\tCount\tValue" endif echohl Title echo printf("%s", title) echohl Normal echo printf("%s", repeat('=', strdisplaywidth(title))) let i=1 for val in max_items for key in keys(res) if res[key] == val && i <= len(max_items) if !empty(b:delimiter) let k = substitute(key, b:delimiter . '\?$', '', '') else let k = key endif if has("float") echo printf("%02d\t%02d\t%2.0f%%\t%.50s", i, res[key], \ ((res[key] + 0.0)/qty)*100, k) else echo printf("%02d\t%02d\t%.50s", i, res[key], k) endif call remove(res,key) let i+=1 else continue endif endfor endfor echo printf("%s", repeat('=', strdisplaywidth(title))) echo printf("different values: %d", len(count_items)) unlet max_items endfunc fu! Vertfold(bang, col) "{{{3 if a:bang do Syntax return endif if !has("conceal") call Warn("Concealing not supported in your Vim") return endif if empty(b:delimiter) && !exists("b:csv_fixed_width_cols") call Warn("There are no columns defined, can't hide away anything!") return endif if empty(a:col) let colnr=WColumn() else let colnr=a:col endif let pat=GetPat(colnr, MaxColumns(), '.*') if exists("b:csv_fixed_width_cols") && \ pat !~ '^\^\.\*' " Make the pattern implicitly start at line start, " so it will be applied by syntax highlighting (:h :syn-priority) let pat='^.*' . pat endif let pat=substitute(pat, '\\zs\(\.\*\)\@=', '', '') if !empty(pat) exe "syn match CSVFold /" . pat . "/ conceal cchar=+" endif endfu fu! InitCSVFixedWidth() "{{{3 if !exists("+cc") " TODO: make this work with a custom matchadd() command for older " Vims, that don't have 'colorcolumn' call 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 ", , , , ..." let char=getchar() while 1 if char == "\" || char == "\" let tcc = eval('tcc'.(char=="\" ? '-' : '+').'1') if tcc < 0 let tcc=0 elseif tcc > max_len let tcc = max_len endif elseif char == "\" || char == 32 " Space let Dict[tcc] = 1 elseif char == "\" || 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 == "\" || char == 27 let &l:cc=_cc redraw! return elseif char == "\" || 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 ", , , , ..." let char=getchar() endw let b:csv_fixed_width_cols=[] let tcc=0 let b:csv_fixed_width_cols = sort(keys(Dict), "SortList") let b:csv_fixed_width = join(sort(keys(Dict), "SortList"), ',') call Init(1, line('$')) let &l:cc=_cc redraw! endfu fu! NewRecord(line1, line2, count) "{{{3 if a:count =~ "\D" call Warn("Invalid count specified") return endif let cnt = (empty(a:count) ? 1 : a:count) let record = "" for item in range(1,MaxColumns()) if !exists("b:col_width") " Best guess width if exists("b:csv_fixed_width_cols") let record .= printf("%*s", 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! MoveOver(outer) "{{{3 " Move over a field " a:outer means include the delimiter let last = 0 let outer_field = a:outer let cur_field = WColumn() let _wsv = winsaveview() if cur_field == 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 MoveCol(-1, line('.')) endif " if cur_field != 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 . "\h" . (outer_field ? "" : "h") . (&sel ==# 'exclusive' ? "l" : '') endif let _wsv.col = col('.')-1 call winrestview(_wsv) let @/ = _s endfu fu! CSVMappings() "{{{3 call Map('noremap', 'W', ':call MoveCol(1, line("."))') call Map('noremap', '', ':call MoveCol(1, line("."))') call Map('noremap', 'L', ':call MoveCol(1, line("."))') call Map('noremap', 'E', ':call MoveCol(-1, line("."))') call Map('noremap', '', ':call MoveCol(-1, line("."))') call Map('noremap', 'H', ':call MoveCol(-1, line("."), 1)') call Map('noremap', 'K', ':call MoveCol(0, line(".")-v:count1)') call Map('noremap', '', ':call MoveCol(0, line(".")-v:count1)') call Map('noremap', 'J', ':call MoveCol(0, line(".")+v:count1)') call Map('noremap', '', ':call MoveCol(0, line(".")+v:count1)') call Map('nnoremap', '', ':call PrepareFolding(1, 1)') call Map('nnoremap', '', ':call PrepareFolding(1, 0)') call Map('nnoremap', '', ':call PrepareFolding(0, 1)') call Map('imap', '', 'ColumnMode()', 'expr') " Text object: Field call Map('vnoremap', 'if', ':call MoveOver(0)') call Map('vnoremap', 'af', ':call MoveOver(1)') call Map('omap', 'af', ':norm vaf') call Map('omap', 'if', ':norm vif') " Remap original values to a sane backup call Map('noremap', 'J', 'J') call Map('noremap', 'K', 'K') call Map('vnoremap', 'W', 'W') call Map('vnoremap', 'E', 'E') call Map('noremap', 'H', 'H') call Map('noremap', 'L', 'L') call Map('nnoremap', '', '') call Map('nnoremap', '', '') call Map('nnoremap', '', '') endfu fu! CommandDefinitions() "{{{3 call LocalCmd("WhatColumn", ':echo WColumn(0)', \ '-bang') call LocalCmd("NrColumns", ':call NrColumns()', '-bang') call LocalCmd("HiColumn", ':call HiCol(,0)', \ '-bang -nargs=?') call LocalCmd("SearchInColumn", \ ':call SearchColumn()', '-nargs=*') call LocalCmd("DeleteColumn", ':call DeleteColumn()', \ '-nargs=? -complete=custom,SortComplete') call LocalCmd("ArrangeColumn", \ ':call ArrangeCol(, , 0, -1)', \ '-range -bang') call LocalCmd("UnArrangeColumn", \':call PrepUnArrangeCol(, )', \ '-range') call LocalCmd("InitCSV", ':call Init(,)', '-range=%') call LocalCmd('Header', \ ':call SplitHeaderLine(,0,1)', \ '-nargs=? -bang') call LocalCmd('VHeader', \ ':call SplitHeaderLine(,0,0)', \ '-nargs=? -bang') call LocalCmd("HeaderToggle", \ ':call SplitHeaderToggle(1)', '') call LocalCmd("VHeaderToggle", \ ':call SplitHeaderToggle(0)', '') call LocalCmd("Sort", \ ':call Sort(0, ,,)', \ '-nargs=* -bang -range=% -complete=custom,SortComplete') call LocalCmd("Column", \ ':call CopyCol(empty()?''"'':,,)', \ '-count -register -nargs=?') call LocalCmd("MoveColumn", \ ':call MoveColumn(,,)', \ '-range=% -nargs=* -complete=custom,SortComplete') call LocalCmd("SumCol", \ ':echo csv#EvalColumn(, "SumColumn", ,)', \ '-nargs=? -range=% -complete=custom,SortComplete') call LocalCmd("ConvertData", \ ':call PrepareDoForEachColumn(,,0)', \ '-bang -nargs=? -range=%') call LocalCmd("Filters", ':call OutputFilters(0)', \ '-nargs=0 -bang') call LocalCmd("Analyze", ':call AnalyzeColumn()', \ '-nargs=?') call LocalCmd("VertFold", ':call Vertfold(0,)', \ '-bang -nargs=? -range=% -complete=custom,SortComplete') call LocalCmd("CSVFixed", ':call InitCSVFixedWidth()', '') call LocalCmd("NewRecord", ':call NewRecord(, \ , )', '-nargs=? -range') call LocalCmd("NewDelimiter", ':call NewDelimiter(, 1, line(''$''))', \ '-nargs=1') call LocalCmd("Duplicates", ':call CheckDuplicates()', \ '-nargs=1 -complete=custom,CompleteColumnNr') call LocalCmd('Transpose', ':call Transpose(, )', \ '-range=%') call LocalCmd('CSVTabularize', ':call Tabularize(0,,)', \ '-bang -range=%') call LocalCmd("AddColumn", \ ':call AddColumn(,,)', \ '-range=% -nargs=* -complete=custom,SortComplete') call LocalCmd('Substitute', ':call SubstituteInColumn(,,)', \ '-nargs=1 -range=%') endfu fu! Map(map, name, definition, ...) "{{{3 let keyname = substitute(a:name, '[<>]', '', 'g') let expr = (exists("a:1") && a:1 == 'expr' ? '' : '') if !get(g:, "csv_nomap_". tolower(keyname), 0) " All mappings are buffer local exe a:map " ". expr a:name a:definition " should already exists if a:map == 'nnoremap' let unmap = 'nunmap' elseif a:map == 'noremap' || a:map == 'map' let unmap = 'unmap' elseif a:map == 'vnoremap' let unmap = 'vunmap' elseif a:map == 'omap' let unmap = 'ounmap' elseif a:map == 'imap' let unmap = 'iunmap' endif let b:undo_ftplugin .= "| " . unmap . " " . a:name endif endfu fu! 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 Aliases if a:name !~ '^CSV' call LocalCmd('CSV'.a:name, a:definition, a:args) endif endfu fu! Menu(enable) "{{{3 if a:enable " Make a menu for the graphical vim amenu CSV.&Init\ Plugin :InitCSV amenu CSV.SetUp\ &fixedwidth\ Cols :CSVFixed amenu CSV.-sep1- amenu &CSV.&Column.&Number :WhatColumn amenu CSV.Column.N&ame :WhatColumn! amenu CSV.Column.&Highlight\ column :HiColumn amenu CSV.Column.&Remove\ highlight :HiColumn! amenu CSV.Column.&Delete :DeleteColumn amenu CSV.Column.&Sort :%Sort amenu CSV.Column.&Copy :Column amenu CSV.Column.&Move :%MoveColumn amenu CSV.Column.S&um :%SumCol amenu CSV.Column.Analy&ze :Analyze amenu CSV.Column.&Arrange :%ArrangeCol amenu CSV.Column.&UnArrange :%UnArrangeCol amenu CSV.Column.&Add :%AddColumn amenu CSV.-sep2- amenu CSV.&Toggle\ Header :HeaderToggle amenu CSV.&ConvertData :ConvertData amenu CSV.Filters :Filters amenu CSV.Hide\ C&olumn :VertFold amenu CSV.&New\ Record :NewRecord else " just in case the Menu wasn't defined properly sil! amenu disable CSV endif endfu fu! SaveOptions(list) "{{{3 let save = {} for item in a:list exe "let save.". item. " = &l:". item endfor return save endfu fu! NewDelimiter(newdelimiter, firstl, lastl) "{{{3 let save = SaveOptions(['ro', 'ma']) if exists("b:csv_fixed_width_cols") call 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 Init(1,line('$')) if exists("_delim") let g:csv_delim = _delim else unlet g:csv_delim endif unlet! _delim endfu fu! IN(list, value) "{{{3 for item in a:list if item == a:value return 1 endif endfor return 0 endfu fu! 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 IN(a:columnlist, i) let key .= column endif let i += 1 endfor if has_key(duplicates, key) && cnt < 10 call Warn("Duplicate Row ". line) let cnt += 1 elseif has_key(duplicates, key) call Warn("More duplicate Rows after: ". line) call Warn("Aborting...") return else let duplicates[key] = 1 endif let line += 1 endwhile if cnt == 0 call Warn("No Duplicate Row found!") endif endfu fu! CompleteColumnNr(A,L,P) "{{{3 return join(range(1,MaxColumns()), "\n") endfu fu! 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 DuplicateRows(list) endfu fu! Transpose(line1, line2) "{{{3 " Note: - Comments will be deleted. " - Does not work with fixed-width columns if exists("b:csv_fixed_width") call 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 = 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 = 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 = 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! NrColumns(bang) "{{{3 if !empty(a:bang) try let cols = MaxColumns(line('.')) catch " No column or comment line call Warn("No valid CSV Column!") endtry else let cols = MaxColumns() endif echo cols endfu fu! Tabularize(bang, first, last) "{{{3 let _c = winsaveview() " Table delimiter definition "{{{4 if !exists("s:td") let s:td = { \ 'hbar': (&enc =~# 'utf-8' ? '─' : '-'), \ 'vbar': (&enc =~# 'utf-8' ? '│' : '|'), \ 'scol': (&enc =~# 'utf-8' ? '├' : '|'), \ 'ecol': (&enc =~# 'utf-8' ? '┤' : '|'), \ 'ltop': (&enc =~# 'utf-8' ? '┌' : '+'), \ 'rtop': (&enc =~# 'utf-8' ? '┐' : '+'), \ 'lbot': (&enc =~# 'utf-8' ? '└' : '+'), \ 'rbot': (&enc =~# 'utf-8' ? '┘' : '+'), \ 'cros': (&enc =~# 'utf-8' ? '┼' : '+'), \ 'dhor': (&enc =~# 'utf-8' ? '┬' : '-'), \ 'uhor': (&enc =~# 'utf-8' ? '┴' : '-') \ } endif "}}}4 if match(getline(a:first), '^'.s:td.ltop) > -1 " Already tabularized, done call 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 CheckHeaderLine() let line=a:first if exists("g:csv_table_leftalign") let b:csv_arrange_leftalign = 1 endif let newlines=[] while line <= a:last let curline = getline(line) if empty(split(curline, b:delimiter)) " only empty delimiters, add one empty delimiter " (:NewDelimiter strips trailing delimiter let curline = repeat(b:delimiter, MaxColumns()) call add(newlines, line) call setline(line, curline) endif let line+=1 endw unlet! line 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 ArrangeCol(a:first, a:last, 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 Warn('An error occured, aborting!') return endif if get(b:, 'csv_arrange_leftalign', 0) call map(b:col_width, 'v:val+1') endif if b:delimiter == "\t" && !get(b:, 'csv_arrange_leftalign',0) 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 NewDelimiter(s:td.vbar, a:first, a:last+adjust_last) "exe printf('sil %d,%ds/%s/%s/ge', a:first, (a:last+adjust_last), " \ (exists("b:csv_fixed_width_cols") ? pat : b:delimiter ), s:td.vbar) " Add vertical bar in first column, if there isn't already one exe printf('sil %d,%ds/%s/%s/e', a:first, a:last+adjust_last, \ '^[^'. s:td.vbar. s:td.scol. ']', s:td.vbar.'&') " And add a final vertical bar, if there isn't one already exe printf('sil %d,%ds/%s/%s/e', a:first, a:last+adjust_last, \ '[^'. s:td.vbar. s:td.ecol. ']$', '&'. s:td.vbar) " Make nice intersection graphs let line = split(getline(a:first), s:td.vbar) call map(line, 'substitute(v:val, ''[^''.s:td.vbar. '']'', s:td.hbar, ''g'')') " Set top and bottom margins call append(a:first-1, s:td.ltop. join(line, s:td.dhor). s:td.rtop) call append(a:last+adjust_last+1, s:td.lbot. join(line, s:td.uhor). s:td.rbot) if s:csv_fold_headerline > 0 call append(a:first + s:csv_fold_headerline, marginline) let adjust_last += 1 endif " Adjust headerline to header of new table let b:csv_headerline = (exists('b:csv_headerline')?b:csv_headerline+2:3) call 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', a:first + s:csv_fold_headerline, a:last + adjust_last, \ '[^'.s:td.scol. '][^'.s:td.hbar.'].*', marginline) endif syn clear let &l:ma = _ma call winrestview(_c) endfu fu! 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 = 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, '\%([\\]\|\[[^]]*\)\@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] = WColumn() elseif columns[-1] == '$' let columns[-1] = maxcolnr endif else " not reached ? call add(columns, WColumn()) endif try if len(cmd) == 1 || columns[0] =~ '\D' || (len(columns) == 2 && columns[1] =~ '\D') call 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 @/ = GetPat(colnr, maxcolnr, cmd[1]) while search(@/) exe printf("%d,%ds//%s%s", a:line1, a:line2, cmd[2], (has_flags ? '/'. cmd[3] : '')) if !has_flags || (has_flags && cmd[3] !~# 'g') break endif endw endfor endif catch /^Vim\%((\a\+)\)\=:E486/ " Pattern not found echohl Error echomsg "E486: Pattern not found in column " . colnr . ": " . pat if &vbs > 0 echomsg substitute(v:exception, '^[^:]*:', '','') endif echohl Normal catch echohl Error "if &vbs > 0 echomsg substitute(v:exception, '^[^:]*:', '','') "endif echohl Normal finally " Restore position and search register call winrestview(_wsv) call call('setreg', _search) endtry endfu fu! 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 "\g`[". (new_line ? "o" : "J".mode) else return "\" endif endfu fu! Timeout(start) "{{{3 return localtime()-a:start < 2 endfu " 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 Warn("File is no CSV file!") return endif let save = winsaveview() call CheckHeaderLine() let nr = matchstr(a:nr, '^\-\?\d\+') let col = (empty(nr) ? 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 = CopyCol('', col, '')[start : stop] " Delete delimiter call map(column, 'substitute(v:val, b:delimiter . "$", "", "g")') " Revmoe trailing whitespace call map(column, 'substitute(v:val, ''^\s\+$'', "", "g")') " Remove leading whitespace call map(column, 'substitute(v:val, ''^\s\+'', "", "g")') " Delete empty values " Leave this up to the function that does something " with each value "call filter(column, '!empty(v:val)') " parse the optional number format let format = matchstr(a:nr, '/[^/]*/') call 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 Warn("Numberformat wrong, needs to be /x:y/!") return '' endif if !empty(s[0]) let s:nr_format[0] = s[0] endif if !empty(s[1]) let s:nr_format[1] = s[1] endif endtry endif try let result=call(function(a:func), [column]) return result catch " Evaluation of expression failed echohl Title echomsg "Evaluating" matchstr(a:func, '[a-zA-Z]\+$') \ "failed for column" col . "!" echohl Normal return '' finally call winrestview(save) endtry endfu " return field index (x,y) with leading/trailing whitespace and trailing " delimiter stripped (only when a:0 is not given) fu! CSVField(x, y, ...) "{{{3 if &ft != 'csv' return endif let y = a:y - 1 let x = (a:x < 0 ? 0 : a:x) let orig = !empty(a:0) let y = (y < 0 ? 0 : y) let x = (x > (MaxColumns()) ? (MaxColumns()) : x) let col = 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 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 = GetPat(a:colnr, MaxColumns(), a:0 ? a:1 : '.*') "let pos = match(pat, '.*\\ze') + 1 " Try to set the cursor at the beginning of the pattern " does not work "call setcmdpos(pos) return pat endfu fu! CSV_WCol(...) "{{{3 try if exists("a:1") && (a:1 == 'Name' || a:1 == 1) return printf("%s", WColumn(1)) else return printf(" %d/%d", WColumn(), MaxColumns()) endif catch return '' endtry endfun fu! CSV_CloseBuffer(buffer) "{{{3 " Setup by SetupQuitPre 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! 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, 'SumColumn', first, last) endfu " 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 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 1707 *ft-csv.txt* For Vim version 7.4 Last Change: Thu, 15 Jan 2015 Author: Christian Brabandt 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| 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 CSVSum()................................|CSVSum()| 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 [] /{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` can be used to highlight Column . 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 , 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[!] 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. 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 [range] is not given, it defaults to the current line. By default, the columns will be righ-aligned. If you want them to be left-aligned, set the buffer variable b:csv_arrange_leftalign = 1 for that particular buffer, e.g. > :let b:csv_arrange_leftalign = 1 < 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). To prevent you from accidently changing your csv file, the buffer will be set 'readonly' afterwards. 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| ). *: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). 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. Instead of a column, you can give the flag 'n' to have it sort numerically. 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 within the range given. If no range is given, this will calculate the sum of the whole column. If 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 numerica 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 only does that at most 10 times). 3.17 Normal mode commands *csv-mapping* ------------------------- The csv filetype plugin redefines the following keys as: or L or W Move [count] field forwards or E or H Move [count] field backwards (but see |csv-mapping-H| for the movement of H). or K Move [count] lines upwards within the same column or J Move [count] lines downwards within the same column Dynamically fold all lines away, that don't match the value in the current column. See |csv-filter| In |Replace-mode| and |Virtual-Replace-mode| does not create a new row, but instead moves the cursor to the beginning of the same column, one more line below. Dynamically fold all lines away, that match the value in the current column. See |csv-filter| Remove last item from the dynamic filter. See |csv-filter| *csv-mapping-H* Note how the mapping of 'H' differs from 'E' H step fields backwards but also stops at where the content of the columns begins. If you look into this example (with the cursor being '|') aaa, bbbb,|ccc ` Pressing 'H' moves to aaa, |bbbb,ccc ` Pressing 'H' again moves to aaa,| bbbb,ccc ` Pressing 'H' again moves to |aaa, bbbb,ccc ` While with 'E', the cursor moves to: aaa,| bbbb,ccc ` and pressing 'E' again, it would move directly to |aaa, bbbb,ccc ` Also, the csv plugin defines these text-object: if Inner Field (contains everything up to the delimiter) af Outer Field (contains everything up to and including the delimiter) Note, that the , , K and J overlap Vim's default mapping for ||, ||, |J| and |K| respectively. Therefore, this functionality has been mapped to a sane default of J and K. If you haven't changed the || or || variables, those the is equival to a single backslash '\', e.g. \K would run the lookup function on the word under the cursor and \J would join this line with the previous line. If you want to prevent the mapping of keys, simply set the global variable g:csv_nomap_ to 1, e.g. to prevent mapping of in csv files, put > let g:csv_nomap_cr = 1 < into your |.vimrc|. Note, the keyname must be lower case. *:CSVConvertData* *ConvertData_CSV* 3.18 Converting a CSV File *csv-convert* -------------------------- You can convert your CSV file to a different format with the `:ConvertData` or `:CSVConvertData` command > ConvertData Use the the ! attribute, to convert your data without the delimiter. This command will interactively ask you for the definition of 3 variables. After which it will convert your csv file into a new format, defined by those 3 variables and open the newly created file in a new window. Those 3 variables define how the text converted. First, You need to define what has to be done, before converting your column data. That is done with the "pre convert" variable. The content of this variable will be put in front of the new document. Second, you define, what has to be put after the converted content of your column data. This happens with the "post convert" variable. Basically the contents of this variable will be put after processing the columns. Last, the columns need to be converted into your format. For this you can specify a printf() format like string, that defines how your data will be converted. You can use '%s' to specify placeholders, which will later be replaced by the content of the actual column. For example, suppose you want to convert your data into HTML, then you first call the > :ConvertData At this point, Vim will ask you for input. First, you need to specify, what needs to be done before processing the data: Pre convert text: ` This would specify to put the HTML Header before the actual data can be processed. If the variable g:csv_pre_convert is already defined, Vim will already show you its' content as default value. Simply pressing Enter will use this data. After that, Vim asks, what the end of the converted file needs to look like: Post convert text:
` So here you are defining how to finish up the HTML file. If the variable g:csv_post_convert is already defined, Vim will already show you its' content as default value which you can confirm by pressing Enter. Last, you define, how your columns need to be converted. Again, Vim asks you for how to do that: Converted text, use %s for column input: ` %s%s%s This time, you can use '%s' expandos. They tell Vim, that they need to be replaced by the actual content of your file. It does by going from the first column in your file and replacing it with the corresponding %s in that order. If there are less '%s' expandos then columns in your file, Vim will skip the columns, that are not used. Again If the variable g:csv_convert is already defined, Vim will already show you its' content as default value which you can confirm by pressing Enter. After you hit Enter, Vim will convert your data and put it into a new window. It may look like this: ` ` `
1,2,3,
2,2,4,
` Note, this is only a proof of concept. A better version of converting your data to HTML is bundled with Vim (|:TOhtml|). But may be you want your data converted into SQL-insert statements. That could be done like this: > ConvertData! < Pre convert text: ` (Leave this empty. It won't be used). Post convert text: Commit; ` After inserting the data, commit it into the database. Converted text, use %s for column input: ` Insert into table foobar values ('%s', '%s', %s); ` Note, that the last argument is not included within single quotation marks, since in this case the data is assumed to be integer and won't need to be quoted for the database. After hitting Enter, a new Window will be opened, which might look like this: Insert into table foobar values('Foobar', '2', 2011); ` Insert into table foobar values('Bar', '1', 2011); ` Commit; ` Since the command was used with the bang attribute (!), the converted data doesn't include the column delimiters. Now you can copy it into your database, or further manipulate it. 3.19 Dynamic filters *csv-filter* -------------------- If you are on a value and only want to see lines that have the same value in this column, you can dynamically filter the file and fold away all lines not matching the value in the current column. To do so, simply press (Enter). Now Vim will fold away all lines, that don't have the same value in this particular row. Note, that leading blanks and the delimiter is removed and the value is used literally when comparing with other values. If you press on the value, all fields having the same value will be folded away. The way this is done is, that the value from the column is extracted and a regular expression for that field is generated from it. In the end this regular expression is used for folding the file. A subsequent or on another value, will add this value to the current applied filter (this is like using the logical AND between the currently active filter and the new value). To remove the last item from the filter, press (backspace). If all items from the filter are removed, folding will be disabled. If some command messes up the folding, you can use |zX| to have the folding being reinitialized. By default, the first line is assumed to be the header and won't be folded away. See also |csv-header|. If you have set the g:csv_move_folds variable and the file is modifiable, all folded lines will be moved to the end of the file, so you can view all non-folded lines as one consecutive area (see also |csv-move-folds|) *:CSVFilter* *:Filter* *Filter_CSV* To see the active filters, you can use the `:Filter` or `:CSVFilter` command. This will show you a small summary, of what filters are active and looks like this: Nr Match Col Name Value ~ ===================================================== ` 01 - 07 Price 23.10 ` 02 + 08 Qty 10 ` This means, there are two filters active. The current active filter is on column 7 (column name is Price) and all values that match 23.10 will be folded away AND all values that don't match a value of 10 in the QTY column will also be folded away. When removing one item from the filter by pressing , it will always remove the last item (highest number in NR column) from the active filter values. Note, that depending on your csv file and the number of filters you used, applying the filter might actually slow down vim, because a complex regular expression is generated that is applied by the fold expression. Look into the @/ (|quote_/|) register to see its value. Use |zX| to apply the current value of your search register as filter. Use > :Filters! to reapply all values from the current active filter and fold non-matching items away. *:CSVAnalyze* *Analyze_CSV* 3.20 Analyze a Column *csv-analyze* --------------------- If you'd like to know, how the values are distributed among a certain column, you can use the `:CSVAnalyze` or `:Analyze` command. So > :Analyze 3 outputs the the distribution of the top 5 values in column 3. This looks like this: Nr Count % Value ~ ============================= ` 01 20 50% 10 ` 02 10 25% 2 ` 03 10 25% 5 ` This tells you, that the the value '10' in column 3 occurs 50% of the time (exactly 20 times) and the other 2 values '2' and '5' occur only 10 times, so 25% of the time. *:CSVVertFold* *VertFold_CSV* 3.21 Vertical Folding *csv-vertfold* --------------------- Sometimes, you want to hide away certain columns to better view only certain columns without having to horizontally scroll. You can use the `:CSVVertFold` or `:VertFold` command to hide certain columns: > :VertFold [] < This will hide all columns from the first until the number entered. It currently can't hide single columns, because of the way, syntax highlighting is used. This command uses the conceal-feature |:syn-conceal| to hide away those columns. If no nr is given, hides all columns from the beginning till the current column. Use > :VertFold! to display all hidden columns again. *:CSVTranspose* *Transpose_CSV* 3.22 Transposing a column *csv-transpose* ------------------------- Transposing means to exchange rows and columns. You can transpose the csv file, using the `:CSVTranspose` or `:Transpose` : > :[range]Transpose < command. If [range] is not given, it will transpose the complete file, otherwise it will only transpose the lines in the range given. Note, comments will be deleted and transposing does not work with fixed-width columns. *:CSVTabularize* 3.23 Transforming into a table *:CSVTable* *csv-tabularize* ------------------------------ You can also transform your csv data into a visual table, using the `:CSVTabularize` or `:CSVTable`: > :CSVTabularize < command. This will make a frame around your csv data and substitute all delimiters by '|', so that it will look like a table. e.g. consider this data: > > First,Second,Third ~ 10,5,2 ` 5,2,10 ` 2,10,5 ` 10,5,2 ` This will be transformed into: > |---------------------| | First| Second| Third| |------|-------|------| | 10| 5| 2| | 5| 2| 10| | 2| 10| 5| | 10| 5| 2| |---------------------| If your Vim uses an unicode 'encoding', the plugin makes a nice table using special unicode drawing glyphs (but it might be possible, that those chars are not being displayed correctly, if either your terminal or the gui font doesn't have characters for those codepoints). If you use the bang form, each row will be separated by a line. You can also visual select a range of lines and use :Tabularize to have only that range converted into a nice ascii table. Else it try to use the current paragraph and try to transform it. If you use the '!' bang argument, between each row, a line will be drawn. In csv files, you can also use the :CSVTabularize command, in different filetypes you can use the :CSVTable command (and is available as plugin so it will be available for non-CSV filetypes). Set the variable g:csv_table_leftalign=1 if you want the columns to be leftaligned. Note: Each row must contain exactly as many fields as columns. *:CSVAddColumn* 3.24 Add new empty columns *AddColumn_CSV* -------------------------- If you want to add new empty columns to your file you can use the `:CSVAddColumn` or `:AddColumn` command: > :[range]AddColumn [column] [count] By default, this works for the whole file, but you can give a different range to which the AddColumn command applies. If no arguments are given, the new empty column will be added after the column on which the cursor is. You can however add as first argument the column number after which the new column needs to be added. Additionally, you can also add a count number to add several columns at once after the specified column number. You 0 for the column number, if you want to add several columns after the current column. *:CSVSubstitute* 3.25 Substitute in columns *Substitute_CSV* -------------------------- If you want to substitute only in specific columns, you can use the `:CSVSubstitute` or `:Substitute` command: > :[range]Substitute [column/]pattern/string[/flags] This means in the range and within the given columns replace pattern by string. This works bascially like the |:s| command, except that you MUST use forward slashes / to delimit the command. The optional part `[column/]` can take either the form of an address or if you leave it out, substitution will only happen in the current column. Additionally, you can use the `1,5/` form to substitute within the columns 1 till 5 or you can even use `1,$` which means to substitute in each column (so in fact this simplifies to a simple `:s` command whithin the given range. For the use of `[/flags]` see |:s_flags| Here are some examples: > :%Substitute 1,4/foobar/baz/gce Substitutes in the whole file in columns 1 till 4 the pattern foobar by baz for every match ('g' flag) and asks for confirmation ('c' flag). :%S 3,$/(\d\+)/\1 EUR/g Substitutes in each column starting from the third each number and appends the EURO suffix to it. ============================================================================== 4. CSV Configuration *csv-configuration* The CSV plugin tries to automatically detect the field delimiter for your file, cause although often the file is called CSV (comma separated values), a semicolon is actually used. The column separator is stored in the buffer-local variable b:delimiter. This delimiter is heavily used, because you need it to define a column. Almost all commands use this variable therefore. 4.1 Delimiter *csv-delimiter* ------------- To override the automatic detection of the plugin and define the separator manually, use: > :let g:csv_delim=',' to let the comma be the delimiter. This sets the buffer local delimiter variable b:delimiter. If your file does not consist of delimited columns, but rather is a fixed width csv file, see |csv-fixedwidth| for configuring the plugin appropriately. If you changed the delimiter, you should reinitiliaze the plugin using |InitCSV| Note: the delimiter will be used to generate a regular expression that matches a column. Therefore, you need to escape special characters. So instead of '^' use '\^'. 4.2 Column *csv-column* ---------- The definition, of what a column is, is defined as buffer-local variable b:col. By default this variable is initialized to: > let b:col='\%(\%([^' . b:delimiter . ']*"[^"]*"[^' . b:delimiter . ']*' \. b:delimiter . '\)\|\%([^' . b:delimiter . ']*\%(' . b:delimiter \. '\|$\)\)\)' This should take care of quoted delimiters within a column. Those should obviously not count as a delimiter. This regular expression is quite complex and might not always work on some complex cases (e.g. linebreaks within a field, see RFC4180 for some ugly cases that will probably not work with this plugin). If you changed the b:delimiter variable, you need to redefine the b:col variable, cause otherwise it will not reflect the change. To change the variable from the comma to a semicolon, you could call in your CSV-Buffer this command: > :let b:col=substitute(b:col, ',', ';', 'g') Check with :echo b:col, if the definition is correct afterwards. You can also force the plugin to use your own defined regular expression as column. That regular expression should include the delimiter for the columns. To define your own regular expression, set the g:csv_col variable: > let g:csv_col='[^,]*,' This defines a column as a field delimited by the comma (where no comma can be contained inside a field), similar to how |csv-strict| works. You should reinitialize the plugin afterwards |InitCSV| 4.3 Highlighting Group *csv-higroup* ---------------------- By default the csv ftplugin uses the WildMenu highlighting Group to define how the |HiColumn| command highlights columns. If you would like to define a different highlighting group, you need to set this via the g:csv_hiGroup variable. You can e.g. define it in your |.vimrc|: > :let g:csv_hiGroup = "IncSearch" You need to restart Vim, if you have changed this variable or use |InitCSV| The |hl-Title| highlighting is used for the Header line that is created by the |Header_CSV| command. If you prefer a different highlighting, set the g:csv_hiHeader variable to the prefered highlighting: > let g:csv_hiHeader = 'Pmenu' < This would set the header window to the |hl-Pmenu| highlighting, that is used for the popup menu. To disable the custom highlighting, simply |unlet| the variable: > unlet g:csv_hiHeader You should reinitialize the plugin afterwards |InitCSV| 4.4 Strict Columns *csv-strict* ------------------ The default regular expression to define a column is quite complex (|csv-column|). This slows down the processing and makes Vim use more memory and it could still not fit to your specific use case. If you know, that in your data file, the delimiter cannot be contained inside the fields quoted or escaped, you can speed up processing (this is quite noticeable when using the |ArrangeColumn_CSV| command) by setting the g:csv_strict_columns variable: > let g:csv_strict_columns = 1 This would define a column as this regex: > let b:col = '\%([^' . b:delimiter . ']*' . b:delimiter . '\|$\)' Much simpler then the default column definition, isn't it? See also |csv-column| and |csv-delimiter| You can disable the effect if you |unlet| the variable: > unlet g:csv_strict_columns You should reinitialize the plugin afterwards |InitCSV| For example when opening a CSV file you get the Error |E363|: pattern uses more memory than 'maxmempattern'. In this case, either increase the 'maxmempattern' or set the g:csv_strict_columns variable. 4.5 Concealing *csv-syntax* *csv-conceal* -------------- The CSV plugin comes with a function to syntax highlight csv files. Basically allt it does is highlight the columns and the header line. By default, the delimiter will not be displayed, if Vim supports |conceal| of syntax items and instead draws a vertical line. If you don't want that, simply set the g:csv_noconceal variable in your .vimrc > let g:csv_no_conceal = 1 and to disable it, simply unlet the variable > unlet g:csv_no_conceal You should reinitialize the plugin afterwards |InitCSV| Note: You can also set the 'conceallevel' option to control how the concealed chars will be displayed. If you want to customize the syntax colors, you can define your own groups. The CSV plugin will use already defined highlighting groups, if they are already defined, otherwise it will define its own defaults which should be visible with 8, 16, 88 and 256 color terminals. For that it uses the CSVColumnHeaderOdd and CSVColumnHeaderEven highlight groups for syntax coloring the first line. All other lines get either the CSVColumnOdd or CSVColumnEven highlighting. In case you want to define your own highlighting groups, you can define your own syntax highlighting like this in your |.vimrc| > hi CSVColumnEven term=bold ctermbg=4 guibg=DarkBlue hi CSVColumnOdd term=bold ctermbg=5 guibg=DarkMagenta hi CSVColumnHeaderEven ... hi CSVColumnHeaderOdd ... < Note, these changes won't take effect, until you restart Vim. 4.6 Newlines *csv-newline* ------------ RFC4180 allows newlines in double quoted strings. By default, the csv-plugin won't recognize newlines inside fields. It is however possible to make the plugin aware of newlines within quoted strings. To enable this, set > let g:csv_nl = 1 and to disable it again, simply unset the variable > unlet g:csv_nl It is a good idea to reinitialize the plugin afterwards |InitCSV| Note, this might not work correctly in all cases. The syntax highlighting seems to change on cursor movements. This could possibly be a bug in the syntax highlighting engine of Vim. Also, |WhatColumn_CSV| can't handle newlines inside fields and will most certainly be wrong. 4.7 Highlight column automatically *csv-hicol* ---------------------------------- You can let vim automatically highlight the column on which the cursor is. This works by defining an |CursorMoved| autocommand to always highlight the column, when the cursor is moved in normal mode. Note, this does not update the highlighting, if the Cursor is moved in Insert mode. To enable this, define the g:csv_highlight_column variable like this > let g:csv_highlight_column = 'y' and to disable it again, simply unset the variable > unlet g:csv_highlight_column It is a good idea to reinitialize the plugin afterwards |InitCSV| 4.8 Fixed width columns *csv-fixedwidth* ----------------------- Sometimes there are no real columns, but rather the file is fixed width with no distinct delimiters between each column. The CSV plugin allows you to handle such virtual columns like csv columns, if you define where each column starts. Note: Except for |ArrangeColumn_CSV| and the |Header_CSV| commands, all commands work in either mode. Those two commands won't do anything in the case of fixedwidth columns, since they don't really make sense here. 4.8.1 Manual setup ------------------ You can do this, by setting the buffer-local variable b:csv_fixed_width like this > let b:csv_fixed_width="1,5,9,13,17,21" This defines that each column starts at multiples of 4. Be sure, to issue this command in the buffer, that contains your file, otherwise, it won't have an effect, since this is a buffer-local option (|local-option|) After setting this variable, you should reinitialize the plugins using |InitCSV| *CSVFixed* 4.8.2 Setup using a Wizard -------------------------- Alternatively, you can setup the fixed width columns using the :CSVFixed command. This provides a simple wizard to select each column. If you enter the command: > :CSVFixed < The first column will be highlighted and Vim outputs: , , , , ... This means, you can now use those 5 keys to configure the fixed-width columns: Use Cursor Left () and Cursor Right () to move the highlighting bar. If you press , this column will be fixed and remain highlighted and there will be another bar, you can move using the Cursor keys. This means this column will be considered to be the border between 2 fixed with columns. Abort Press the backspace key, to remove the last column you fixed with the key. Use Enter to finish the wizard. This will use all fixed columns to define the fixed width columns of your csv file. The plugin will be initialized and syntax highlighting should appear. Note: This only works, if your Vim has the 'colorcolumn' option available (This won't work with Vim < 7.3 and also not with a Vim without +syntax feature). 4.9 CSV Header lines *csv-header* -------------------- By default, dynamic filtering |csv-filter| will not fold away the first line. If you don't like that, you can define your header line using the variable b:csv_fold_headerline, e.g. > let b:csv_headerline = 0 to disable, that a header line won't be folded away. If your header line instead is on line 5, simply set this variable to 5. This also applies to the |Header_CSV| command. 4.10 Number format *csv-nrformat* ------------------ When using the |SumCol_CSV| command, you can specify a certain number format using the /x:y/ argument. You can however also configure the plugin to detect a different number format than the default number format (which does not support a thousands separator and uses the '.' as decimal separator). To specify a different thousands separator by default, use > let b:csv_thousands_sep = ' ' to have the space use as thousands separator and > let b:csv_decimal_sep = ',' to use the comma as decimal separator. 4.11 Move folded lines *csv-move-folds* ---------------------- If you use dynamic filters (see |csv-filter|), you can configure the plugin to move all folded lines to the end of the file. This only happens if you set the variable > let g:csv_move_folds = 1 < and the file is modifiable. This let's you see all non-folded records as a consecutive area without being disrupted by folded lines. 4.12 Using comments *csv-comments* ------------------- Strictly speaking, in csv files there can't be any comments. You might however still wish to comment or annotate certain sections in your file, so the CSV plugin supports Comments. Be default, the CSV plugin will use the 'commentstring' setting to identify comments. If this option includes the '%s' it will consider the part before the '%s' as leading comment marker and the part behind it as comment delimiter. You can however define your own comment marker, using the variable g:csv_comment. Like with the 'commentstring' setting, you can use '%s' expandos, that will denote where the actual comment text belongs. To define your own comment string, put this in your |.vimrc| > :let g:csv_comment = '#' < Which will use the '#' sign as comment leader like in many scripting languages. After setting this variable, you should reinitialize the plugins using |InitCSV| *csv-foldtext* By default, the csv plugin sets the 'foldtext' option. If you don't want this, set the variable `g:csv_disable_fdt` in your |.vimrc| > :let g:csv_disable_fdt = 1 ============================================================================== 5. Functions *CSV-Functions* The csv plugins also defines some functions, that can be used for scripting when a csv file is open 5.1 CSVPat() *CSVPat()* ------------ CSVPat({column}[, {pattern}]) This function returns the pattern for the selected column. If only columns is given, returns the regular expression used to search for the pattern '.*' in that column (which means the content of that column). Alternatively, an optional pattern can be given, so the return string can be directly feeded to the |/| or |:s| command, e.g. type: > :s/=CSVPat(3, 'foobar')/baz where the means pressing Control followed by R followed by = (see |c_CTRL-R_=|). A prompt will apear, with the '=' as the first character on which you can enter expressions. In this case enter CSVPat(3, 'foobar') which returns the pattern to search for the string 'foobar' in the third column. After you press enter, the returned pattern will be put after the :s command so you can directly enter / and the substitute string. 5.2 CSVField(x,y[, orig]) *CSVField()* ------------------------- This function returns the field at index (x,y) (starting from 1). If the parameter orig is given, returns the column "as is" (e.g. including delimiter and leading and trailing whitespace, otherwise that will be stripped.) 5.3 CSVCol([name]) *CSVCol()* ------------------ If the name parameter is given, returns the name of the column, else returns the index of the current column, starting at 1. 5.4 CSVSum(col, fmt, startline, endline) *CSVSum()* ---------------------------------------- Returns the sum for column col. Uses fmt to parse number format (see |:CSVSumCol|) startline and endline specify the lines to consider, if empty, will be first and last line. ============================================================================== 6. CSV Tips and Tricks *csv-tips* Here, there you'll find some small tips and tricks that might help when working with CSV files. 6.1 Statusline *csv-stl* -------------- Suppose you want to include the column, on which the cursor is, into your statusline. You can do this, by defining in your .vimrc the 'statusline' like this: > function MySTL() if has("statusline") hi User1 term=standout ctermfg=0 ctermbg=11 guifg=Black guibg=Yellow let stl = ... if exists("*CSV_WCol") let csv = '%1*%{&ft=~"csv" ? CSV_WCol() : ""}%*' else let csv = '' endif return stl.csv endif endfunc set stl=%!MySTL() < This will draw in your statusline right aligned the current column and max column (like 1/10), if you are inside a CSV file. The column info will be drawn using the User1 highlighting (|hl-User1|), that has been defined in the second line of the function. In the third line of your function, put your desired 'statusline' settings as |expression|. Note the section starting with 'if exists(..)' guards against not having loaded the filetype plugin. Note: vim-airline (https://github.com/bling/vim-airline) by default supports the csv plugin and enables a nice little csv statusline which helps for navigating within a csv file. For details, see the Vim-Airline documentation. *CSV_WCol()* The CSV_WCol() function controls, what will be output. In the simplest case, when no argument is given, it simply returns on which column the cursor is. This would look like '1/10' which means the cursor is on the first of 10 columns. If you rather like to know the name of the column, simply give as parameter to the function the string "Name". This will return the column name as it is printed on the first line of that column. This can be adjusted, to have the column name printed into the statusline (see |csv-stl| above) by replacing the line > let csv = '%1*%{&ft=~"csv" ? CSV_WCol() : ""}%*' < by e.g. let csv = '%1*%{&ft=~"csv" ? CSV_WCol("Name") . " " . CSV_WCol() : ""}%*' which will output "Name 2/10" if the cursor is in the second column which is named "Name". 6.2 Slow CSV plugin *csv-slow* ------------------- Processing a csv file using |ArrangeColumn_CSV| can be quite slow, because Vim needs to calculate the width for each column and then replace each column by itself widened by spaces to the optimal length. Unfortunately, csv files tend to be quite big. Remember, for a file with 10,000 lines and 50 columns Vim needs to process each cell, which accumulates to 500,000 substitutions. It might take some time, until Vim is finished. You can speed up things a little bit, if you omit the '!' attribute to the |ArrangeColumn| (but this will only work, if the width has been calculated before, e.g. by issuing a :1ArrangeColumn command to arrange only the first line. Additionally you can also configure how this command behaves by setting some configuration variables. Also note, using dynamic filters (|csv-filter|), can slow down Vim considerably, since they internally work with complex regular expressions, and if you have a large file, containing many columns, you might hit a performance penalty (especially, if you want to filter many columns). It's best to avoid those functions if you are using a large csv file (so using strict columns |csv-strict| might help a little and also setting 're' to 1 might also alleviate it a little). 6.3 Defining custom aggregate functions *csv-aggregate-functions* --------------------------------------- The CSV plugin already defines the |SumCol_CSV| command, to let you calculate the sum of all values of a certain column within a given range. This will consider all values within the range, that are not folded away (|csv-filter|), and also skip comments and the header lines. The delimiter will be deleted from each field. But it may be, that you don't need the sum, but would rather want to have the average of all values within a certain column. You can define your own function and let the plugin call it for a column like this: 1) You define your own custom function in the after directory of your vim runtime path |after-directory| (see also #2 below) > fun! My_CSV_Average(col) let sum=0 for item in a:col let sum+=item endfor return sum/len(a:col) endfun < This function takes a list as argument, and calculates the average for all items in the list. You could also make use of Vim's |eval()| function and write your own Product function like this > fun! My_CSV_Product(col) return eval(join(a:col, '*')) endfun < 2) Now define your own custom command, that calls your custom function for a certain column > command! -buffer -nargs=? -range=% AvgCol \ :echo csv#EvalColumn(, \ "My_CSV_Average", ,) < This command should best be put into a file called csv.vim and save it into your ~/.vim/after/ftplugin/ directory. Create directories that don't exist yet. For Windows, this would be the $VIMRUNTIME/vimfiles/after/ftplugin directory. 3) Make sure, your |.vimrc| includes a filetype plugin setting like this > filetype plugin on < This should make sure, that all the necessary scripts are loaded by Vim. After restarting Vim, you can now use your custom command definition :AvgCol. Use a range, for the number of lines you want to evaluate and optionally use an argument to specify which column you want to be evaluated > :2,$AvgCol 7 < This will evaluate the average of column seven (assuming, line 1 is the header line, which should not be taken into account). 6.4 Autocommand on opening/closing files *csv-arrange-autocmd* ---------------------------------------- If you want your CSV files to always be displayed like a table, you can achieve this using the |ArrangeColumn_CSV| command and some autocommands. Define these autocommands in your |.vimrc| > aug CSV_Editing au! au BufRead,BufWritePost *.csv :%ArrangeColumn au BufWritePre *.csv :%UnArrangeColumn aug end Upon Entering a csv file, Vim will visually arrange all columns and before writing, those columns will be collapsed again. The BufWritePost autocommand makes sure, that after the file has been written successfully, the csv file will again be visually arranged. You can also simply set the variable > let g:csv_autocmd_arrange = 1 < in your vimrc and an autocmd will be installed, that visually arranges your csv file whenever you open them for editing. Alternatively, you can restrict this setting to files below a certain size. For example, if you only want to enable this feature for files smaller than 1 MB, put this into your |.vimrc| > let g:csv_autocmd_arrange = 1 let g:csv_autocmd_arrange_size = 1024*1024 Note, this is highly experimental and especially on big files, this might slow down Vim considerably. 6.5 Syntax error when opening a CSV file *csv-syntax-error* ---------------------------------------- This happens usually, when the syntax script is read before the filetype plugin, so the plugin did not have a chance to setup the column delimiter correctly. The easy way to fix it, is to reverse the order of the :syntax on (|:syn-on|) and :filetype plugin (|:filetype-plugin-on|) statements in your |.vimrc| Alternatively, you can simply call |InitCSV| and ignore the error. 6.6 Calculate new columns *csv-calculate-column* ------------------------- Suppose you have a table like this: Index;Value1;Value2~ 1;100;3 ` 2;20;4 ` And you need one more column, that is the calculated product of column 2 and 3, you can make use of the provided |CSVField()| function using a |sub-replace-expression| of an |:s| command. In this case, you would do this: > :2,3s/$/\=printf("%s%.2f", b:delimiter, (CSVField(2,line('.'))+0.0)*(CSVField(3,line('.'))+0.0/ Note: Enter as single line. The result will be this: > Index;Value1;Value2~ 1;100;3;300.00 ` 2;20;4;80.00 ` ============================================================================== 7. CSV Changelog *csv-changelog* 0.31 Jan 15, 2015 "{{{1 - fix that H on the very first cell, results in an endless loop (https://github.com/chrisbra/csv.vim/issues/31, reported by lahvak, thanks!) - fix that count for |AddColumn| did not work (according to the documentation) (https://github.com/chrisbra/csv.vim/issues/32, reported by lahvak, thanks!) - invalid reference to a WarningMsg() function - WhatColumn! error, if the first line did not contain as many fields as the line to check. - Rename |:Table| command to |:CSVTable| ( https://github.com/chrisbra/csv.vim/issues/33, reported by Peter Jaros, thanks!) - Mention to escape special characters when manually specifying the delimiter. https://github.com/chrisbra/csv.vim/issues/35), also detect '^' as delimiter. - Csv fixed with columns better use '\%v' to match columns, otherwise, one could get problems with multibyte chars - Sorting should work better with csv fixed with patterns (could generate an inavlide pattern before) - Refactor GetSID() (provided by Ingo Karkat https://github.com/chrisbra/csv.vim/pull/37, thanks!) - New public function |CSVSum()| - Restrict |csv-arrange-autocmd| to specific file sizes (suggested by Spencer Boucher in https://github.com/chrisbra/csv.vim/issues/39, thanks!) - Make |:CSVSearchInColumn| wrap pattern in '%\(..\)' pairs, so it works correctly with '\|' atoms - Small improvements on |:CSVTable| and |:NewDelimiter| command - and should skip folds (like in normal Vi mode, suggested by Kamaraju Kusuma, thanks!) 0.30 Mar 27, 2014 {{{1 - |:CSVSubstitute| should substitute all matches in a column, when 'g' flag is given - Don't override 'fdt' setting (https://github.com/chrisbra/csv.vim/issues/18, reported by Noah Frederick, thanks!) - Consistent Commands naming (https://github.com/chrisbra/csv.vim/issues/19, reported by Noah Frederick, thanks!) - New Function |CSVField()| and |CSVCol()| - clean up function did not remove certain buffer local variables, possible error when calling Menu function to disable CSV menu - make |:CSVArrangeColumn| do not output the numer of substitutions happened (suggested by Caylan Larson, thanks!) - better cleaning up on exit, if Header windows were used - Let |:CSVVHeader| accept a number, of how many columns to show (suggested by Caylan Larson, thanks!) - better error-handling for |CSVFixed| - selection of inner/outer text objects was wrong, reported by Ingo Karkat, thanks!) - errors, when using |:CSVAnalyze| and there were empty attributes - allow to left-align columns when using |:CSVArrangeColumn| - |SumCol_CSV| did not detect negative values - make in (Virtual-) Replace work as documented 0.29 Aug 14, 2013 {{{1 - setup |QuitPre| autocommand to quit cleanly in newer vims when using :Header and :VHeader - new |AddColumn_CSV| command - prevent mapping of keys, if g:csv_nomap_ is set (reported by ping) - new |Substitute_CSV| command - better syntax highlighting - small speedup for |ArrangeColumn_CSV| - 'E' did not correctly move the the previous column - support for vim-airline added 0.28 Dec 14, 2012 {{{1 - new command :Table to create ascii tables for non-csv files 0.27 Nov 21, 2012 {{{1 - Better |CSV-Tabularize| - Documentation update 0.26 Jul 25, 2012 {{{1 - Better handling of setting filetype specific options - |CSV-Tabularize| - fix some small errors 0.25 May 17, 2012 {{{1 - |SearchInColumn_CSV| should match non-greedily, patch by Matěj Korvas, - better argument parsing for |SearchInColumn_CSV|, patch by Matěj Korvas, thanks! 0.24 Apr 12, 2012 {{{1 - Allow to transpose the file (|csv-transpose|, suggested by Karan Mistry, thanks!) - |DeleteColumn_CSV| allows to specify a search pattern and all matching columns will be deleted (suggested by Karan Mistry, thanks!) 0.23 Mar 25, 2012 {{{1 - Don't error out, when creating a new file and syntax highlighting script can't find the delimiter (ftplugin will still give a warning, so). - Don't pollute the search register when loading a file - Give Warning when number format is wrong - Don't source ftdetect several times (patch by Zhao Cai, thanks!) - |NewDelimiter_CSV| to change the delimiter of the file - |Duplicate_CSV| to check for duplicate records in the file - Issue https://github.com/chrisbra/csv.vim/issues/13 fixed (missing quote, reported by y, thanks!) - |CSVPat()| function - 'lz' does not work with |:silent| |:s| (patch by Sergey Khorev, thanks!) - support comments (|csv_comment|, suggested by Peng Yu, thanks!) 0.22 Nov 08, 2011 {{{1 - Small enhancements to |SumCol_CSV| - :Filters! reapplys the dynamic filter - Apply |csv-aggregate-functions| only to those values, that are not folded away. - |SumCol_CSV| can use a different number format (suggested by James Cole, thanks! (also |csv-nrformat| - Documentation updates (suggested by James Cole and Peng Yu) - More code cleanup and error handling https://github.com/chrisbra/csv.vim/issues/9 reported Daniel Carl, thanks! https://github.com/chrisbra/csv.vim/issues/8 patch by Daniel Carl, thanks! - New Command |NewRecord_CSV| (suggest by James Cole, thanks!) - new textobjects InnerField (if) and outerField (af) which contain the field without or with the delimiter (suggested by James Cole, thanks!) - |csv-arrange-autocmd| to let Vim automatically visually arrange the columns using |ArrangeColumn_CSV| - |csv-move-folds| let Vim move folded lines to the end - implement a Menu for graphical Vim 0.21 Oct 06, 2011 {{{1 - same as 0.20 (erroneously uploaded to vim.org) 0.20 Oct 06, 2011 {{{1 - Implement a wizard for initializing fixed-width columns (|CSVFixed|) - Vertical folding (|VertFold_CSV|) - fix plugin indentation (by Daniel Karl, thanks!) - fixed missing bang parameter for HiColumn function (by Daniel Karl, thanks!) - fixed broken autodection of delimiter (reported by Peng Yu, thanks!) 0.19 Sep 26, 2011 {{{1 - Make |:ArrangeColumn| more robust - Link CSVDelimiter to the Conceal highlighting group for Vim's that have +conceal feature (suggested by John Orr, thanks!) - allow the possibility to return the Column name in the statusline |csv-stl| (suggested by John Orr, thanks!) - documentation updates - Allow to dynamically add Filters, see |csv-filter| - Also display what filters are active, see |:Filter| - Analyze a column for the distribution of a value |csv-analyze| - Implement UnArrangeColumn command |UnArrangeColumn_CSV| (suggested by Daniel Karl in https://github.com/chrisbra/csv.vim/issues/7) 0.18 Aug 30, 2011 {{{1 - fix small typos in documentation - document, that 'K' and 'J' have been remapped and the originial function is available as \K and \J - Delimiters should not be highlighted within a column, only when used as actual delimiters (suggested by Peng Yu, thanks!) - Performance improvements for |:ArrangeColumn| 0.17 Aug 16, 2011 {{{1 - small cosmetic changes - small documentation updates - fold away changelog in help file - Document, that |DeleteColumn_CSV| deletes the column on which the cursor is, if no column number has been specified - Support csv fixed width columns (|csv-fixedwidth|) - Support to interactively convert your csv file to a different format (|csv-convert|) 0.16 Jul 25, 2011 {{{1 - Sort on the range, specified (reported by Peng Yu, thanks!) - |MoveCol_CSV| to move a column behind another column (suggested by Peng Yu, thanks!) - Document how to use custom functions with a column (|csv-aggregate-functions|) - Use g:csv_highlight_column variable, to have Vim automatically highlight the column on which the cursor is (|csv-hicol|) - Header/VHeader command should work better now (|Header_CSV|, |VHeader_CSV|) - Use setreg() for setting the register for the |Column_CSV| command and make sure it is blockwise. - Release 0.14 was not correctly uploaded to vim.org 0.14 Jul 20, 2011 {{{1 - really use g:csv_no_conceal variable (reported by Antonio Ospite, thanks!) - Force redrawing before displaying error messages in syntax script (reported by Antonio Ospite, thanks!) - Make syntax highlighting work better with different terminals (Should work now with 8, 88 and 256 color terminals, tested with linux konsole, xterm and rxvt) (https://github.com/chrisbra/csv.vim/issues/4) - Automatically detect '|' as field separator for csv files 0.13 Mar 14, 2011 {{{1 - documentation update - https://github.com/chrisbra/csv.vim/issues#issue/2 ('splitbelow' breaks |Header_CSV|, fix this; thanks lespea!) - https://github.com/chrisbra/csv.vim/issues#issue/3 ('gdefault' breaks |ArrangeColumn_CSV|, fix this; thanks lespea!) - https://github.com/chrisbra/csv.vim/issues#issue/1 (make syntax highlighting more robust, thanks lespea!) - fix some small annoying bugs - WhatColumn! displays column name 0.12 Feb 24, 2011 {{{1 - bugfix release: - don't use |:noa| when switching between windows - make sure, colwidth() doesn't throw an error 0.11 Feb 24, 2011 {{{1 - new command |Copy_CSV| - |Search_CSV| did not find anything in the last column if no delimiter was given (reported by chroyer) - |VHeader_CSV| display the first column as Header similar to how |Header_CSV| works - |HeaderToggle_CSV| and |VHeaderToggle_CSV| commands that toggle displaying the header lines/columns 0.10 Feb 23, 2011 {{{1 - Only conceal real delimiters - document g:csv_no_conceal variable - document g:csv_nl variable - document conceal feature and syntax highlighting - Normal mode command / work like K/J - More robust regular expression engine, that can also handle newlines inside quoted strings. - Slightly adjusted syntax highlighting 0.9 Feb 19, 2011 {{{1 - use conceal char depending on encoding - Map normal mode keys also for visual/select and operator pending mode 0.8 Feb 17, 2011 {{{1 - Better Error handling - HiColumn! removes highlighting - Enable NrColumns, that was deactivated in v.0.7 - a ColorScheme autocommand makes sure, that the syntax highlighting is reapplied, after changing the colorscheme. - SearchInColumn now searches in the current column, if no column has been specified - A lot more documentation - Syntax Highlighting conceales delimiter - small performance improvements for |ArrangeColumn_CSV| 0.7 Feb 16, 2011 {{{1 - Make the motion commands 'W' and 'E' work more reliable - Document how to setup filetype plugins - Make |WhatColumn_CSV| work more reliable (report from http://vim.wikia.com/Script:3280) - DeleteColumn deletes current column, if no argument given - |ArrangeColumn_CSV| handles errors better - Code cleanup - Syntax highlighting - 'H' and 'L' move forward/backwards between csv fields - 'K' and 'J' move upwards/downwards within the same column - |Sort_CSV| to sort on a certain column - |csv-tips| on how to colorize the statusline 0.6 Feb 15, 2011 {{{1 - Make |ArrangeColumn_CSV| work more reliable (had problems with multibyte chars before) - Add |Header_CSV| function - 'W' and 'E' move forward/backwards between csv fields - provide a file ftdetect/csv.vim to detect csv files 0.5 Apr 20 2010 {{{1 - documentation update - switched to a public repository: http://github.com/chrisbra/csv.vim - enabled GLVS (see |GLVS|) 0.4a Mar 11 2010 {{{1 - fixed documentation 0.4 Mar 11 2010 {{{1 - introduce |InitCSV| - better Error handling - HiColumn now by default highlights the current column, if no argument is specified. 0.3 Oct, 28 2010 {{{1 - initial Version vim:tw=78:ts=8:ft=help:norl:et:fdm=marker:fdl=0 syntax/csv.vim [[[1 158 " A simple syntax highlighting, simply alternate colors between two " adjacent columns " Init {{{2 let s:cpo_save = &cpo set cpo&vim scriptencoding utf8 if version < 600 syn clear elseif exists("b:current_syntax") finish endif " Helper functions "{{{2 fu! Warning(msg) "{{{3 " Don't redraw, so we are not overwriting messages from the ftplugin " script "redraw! echohl WarningMsg echomsg "CSV Syntax:" . a:msg echohl Normal endfu fu! Esc(val, char) "{{2 return '\V'.escape(a:val, '\\'.a:char).'\m' endfu fu! CheckSaneSearchPattern() "{{{3 let s:del_def = ',' let s:col_def = '\%([^' . s:del_def . ']*' . s:del_def . '\|$\)' let s:col_def_end = '\%([^' . s:del_def . ']*' . s:del_def . '\)' " First: " Check for filetype plugin. This syntax script relies on the filetype " plugin, else, it won't work properly. redir => s:a |sil filetype | redir end let s:a=split(s:a, "\n")[0] if match(s:a, '\cplugin:off') > 0 call Warning("No filetype support, only simple highlighting using" \ . s:del_def . " as delimiter! See :h csv-installation") endif " Check Comment setting if !exists("g:csv_comment") let b:csv_cmt = split(&cms, '%s') else let b:csv_cmt = split(g:csv_comment, '%s') endif " Second: Check for sane defaults for the column pattern " Not necessary to check for fixed width columns if exists("b:csv_fixed_width_cols") return endif " Try a simple highlighting, if the defaults from the ftplugin " don't exist let s:col = exists("b:col") && !empty(b:col) ? b:col \ : s:col_def let s:col_end = exists("b:col_end") && !empty(b:col_end) ? b:col_end \ : s:col_def_end let s:del = exists("b:delimiter") && !empty(b:delimiter) ? b:delimiter \ : s:del_def let s:cmts = exists("b:csv_cmt") ? b:csv_cmt[0] : split(&cms, '&s')[0] let s:cmte = exists("b:csv_cmt") && len(b:csv_cmt) == 2 ? b:csv_cmt[1] \ : '' if line('$') > 1 && (!exists("b:col") || empty(b:col)) " check for invalid pattern, ftplugin hasn't been loaded yet call Warning("Invalid column pattern, using default pattern " . s:col_def) endif endfu " Syntax rules {{{2 fu! DoHighlight() "{{{3 if has("conceal") && !exists("g:csv_no_conceal") && \ !exists("b:csv_fixed_width_cols") " old val "\ '\%(.\)\@=/ms=e,me=e contained conceal cchar=' . " Has a problem with the last line! exe "syn match CSVDelimiter /" . s:col_end . \ '/ms=e,me=e contained conceal cchar=' . \ (&enc == "utf-8" ? "│" : '|') "exe "syn match CSVDelimiterEOL /" . s:del . " \ '\?$/ contained conceal cchar=' . " \ (&enc == "utf-8" ? "│" : '|') hi def link CSVDelimiter Conceal elseif !exists("b:csv_fixed_width_cols") " The \%(.\)\@<= makes sure, the last char won't be concealed, " if it isn't a delimiter "exe "syn match CSVDelimiter /" . s:col . '\%(.\)\@<=/ms=e,me=e contained' exe "syn match CSVDelimiter /" . s:col_end . '/ms=e,me=e contained' "exe "syn match CSVDelimiterEOL /" . s:del . '\?$/ contained' if has("conceal") hi def link CSVDelimiter Conceal else hi def link CSVDelimiter Ignore endif endif " There is no delimiter for csv fixed width columns if !exists("b:csv_fixed_width_cols") exe 'syn match CSVColumnEven nextgroup=CSVColumnOdd /' \ . s:col . '/ contains=CSVDelimiter' exe 'syn match CSVColumnOdd nextgroup=CSVColumnEven /' \ . s:col . '/ contains=CSVDelimiter' exe 'syn match CSVColumnHeaderEven nextgroup=CSVColumnHeaderOdd /\%<'. (get(b:, 'csv_headerline', 1)+1).'l' \. s:col . '/ contains=CSVDelimiter' exe 'syn match CSVColumnHeaderOdd nextgroup=CSVColumnHeaderEven /\%<'. (get(b:, 'csv_headerline', 1)+1).'l' \. s:col . '/ contains=CSVDelimiter' else for i in range(len(b:csv_fixed_width_cols)) let pat = '/\%' . b:csv_fixed_width_cols[i] . 'v.*' . \ ((i == len(b:csv_fixed_width_cols)-1) ? '/' : \ '\%' . b:csv_fixed_width_cols[i+1] . 'v/') let group = "CSVColumn" . (i%2 ? "Odd" : "Even" ) let ngroup = "CSVColumn" . (i%2 ? "Even" : "Odd" ) exe "syn match " group pat " nextgroup=" . ngroup endfor endif " Comment regions exe 'syn match CSVComment /'. Esc(s:cmts, '/'). '.*'. \ (!empty(s:cmte) ? '\%('. Esc(s:cmte, '/'). '\)\?' \: ''). '/' hi def link CSVComment Comment endfun fu! DoSyntaxDefinitions() "{{{3 syn spell toplevel " Not really needed syn case ignore hi def link CSVColumnHeaderOdd WarningMsg hi def link CSVColumnHeaderEven WarningMsg hi def link CSVColumnOdd DiffAdd hi def link CSVColumnEven DiffChange endfun " Main: {{{2 " Make sure, we are using a sane, valid pattern for syntax " highlighting call CheckSaneSearchPattern() " Define all necessary syntax groups call DoSyntaxDefinitions() " Highlight the file call DoHighlight() " Set the syntax variable {{{2 let b:current_syntax="csv" let &cpo = s:cpo_save unlet s:cpo_save ftdetect/csv.vim [[[1 3 " Install Filetype detection for CSV files au BufRead,BufNewFile *.csv,*.dat,*.tsv,*.tab set filetype=csv plugin/csv.vim [[[1 86 if exists('g:loaded_csv') && g:loaded_csv finish endif let g:loaded_csv = 1 let s:cpo_save = &cpo set cpo&vim if exists("g:csv_autocmd_arrange") if !exists("*CSVDoBufLoadAutocmd") fu! CSVDoBufLoadAutocmd() " Visually arrange columns when opening a csv file aug CSV_Edit au! au BufReadPost,BufWritePost *.csv,*.dat,*.tsv,*.tab :exe \ printf(":call CSVArrangeCol(1, %d, 0, %d)", \ line('$'), get(g:, 'csv_autocmd_arrange_size', -1)) au BufWritePre *.csv,*.dat,*.tsv,*.tab :sil %UnArrangeColumn aug end endfu call CSVDoBufLoadAutocmd() endif endif com! -range -bang -nargs=? CSVTable call Table(0, , , ) fu! Table(bang, line1, line2, delim) " save and restore some options if has("conceal") let _a = [ &l:lz, &l:syntax, &l:ft, &l:sol, &l:tw, &l:wrap, &l:cole, &l:cocu, &l:fen, &l:fdm, &l:fdl, &l:fdc, &l:fml, &l:fdt, &l:ma, &l:ml] else let _a = [ &l:lz, &l:syntax, &l:ft, &l:sol, &l:tw, &l:wrap, &l:fen, &l:fdm, &l:fdl, &l:fdc, &l:fml, &l:fdt, &l:ma, &l:ml] endif let _b = winsaveview() let line1 = a:line1 let line2 = a:line2 if line1 == line2 " use the current paragraph let line1 = line("'{") + 1 let line2 = line("'}") - 1 endif if !empty(a:delim) let g:csv_delim = (a:delim ==# '\t' ? "\t" : a:delim) endif " try to guess the delimiter from the specified region, therefore, we need " to initialize the plugin to inspect only those lines let [ b:csv_start, b:csv_end ] = [ line1, line2 ] " Reset b:did_ftplugin just to be sure unlet! b:did_ftplugin setl noml ft=csv lz ma " get indent let indent = matchstr(getline(a:line1), '^\s\+') exe printf(':sil %d,%ds/^\s\+//e', line1, line2) let last = line('$') try let b:csv_list=getline(line1, line2) call filter(b:csv_list, '!empty(v:val)') call map(b:csv_list, 'split(v:val, b:col.''\zs'')') if exists(":CSVTabularize") exe printf("%d,%dCSVTabularize%s", line1, line2, empty(a:bang) ? '' : '!') else echoerr "Not possible to call :CSVTabularize" endif unlet! b:col_width b:csv_list catch finally if !empty(indent) " Added one line above a:line1 and several lines below, so need to " correct the range exe printf(':sil %d,%ds/^/%s/e', (line1 - 1), (line2 + line('$') - last), indent) endif if has("conceal") let [ &l:lz, &l:syntax, &l:ft, &l:sol, &l:tw, &l:wrap, &l:cole, &l:cocu, &l:fen, &l:fdm, &l:fdl, &l:fdc, &l:fml, &l:fdt, &l:ma, &l:ml] = _a else let [ &l:lz, &l:syntax, &l:ft, &l:sol, &l:tw, &l:wrap, &l:fen, &l:fdm, &l:fdl, &l:fdc, &l:fml, &l:fdt, &l:ma, &l:ml] = _a endif unlet! g:csv_delim call winrestview(_b) endtry endfu let &cpo = s:cpo_save unlet s:cpo_save