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