From 2c209be16f0eae0ad2a84b66debdc15ea15f18fe Mon Sep 17 00:00:00 2001 From: Stefan Liebl Date: Thu, 28 Apr 2016 14:15:55 +0200 Subject: [PATCH] Update csv.vim Change-Id: Iff4fea84dbd1f64b09b02cd1c65f3fb9760cefe2 --- vimfiles/doc/ft-csv.txt | 242 ++++++++- vimfiles/ftplugin/csv.vim | 1012 +++++++++++++++++++++++-------------- vimfiles/plugin/csv.vim | 7 + vimfiles/syntax/csv.vim | 52 +- 4 files changed, 906 insertions(+), 407 deletions(-) diff --git a/vimfiles/doc/ft-csv.txt b/vimfiles/doc/ft-csv.txt index c73fc44..fe114e2 100644 --- a/vimfiles/doc/ft-csv.txt +++ b/vimfiles/doc/ft-csv.txt @@ -35,6 +35,8 @@ NO WARRANTY, EXPRESS OR IMPLIED. USE AT-YOUR-OWN-RISK. 3.23 Transforming into a table..............|csv-tabularize| 3.24 Add new empty columns..................|AddColumn_CSV| 3.25 Substitute in columns..................|Substitute_CSV| + 3.26 Count values inside a column...........|Count_CSV| + 3.27 Maximum/Minimum values ................|MaxCol_CSV| 4. CSV Filetype configuration...................|csv-configuration| 4.1 Delimiter...............................|csv-delimiter| 4.2 Column..................................|csv-column| @@ -54,7 +56,9 @@ NO WARRANTY, EXPRESS OR IMPLIED. USE AT-YOUR-OWN-RISK. 5.1 CSVPat()................................|CSVPat()| 5.2 CSVField()..............................|CSVField()| 5.3 CSVCol()................................|CSVCol()| - 5.4 CSVSum()................................|CSVSum()| + 5.4 CSVCount()..............................|CSVCount()| + 5.4 CSVMax()................................|CSVMax()| + 5.4 CSVMin()................................|CSVMin()| 6. CSV Tips and Tricks..........................|csv-tips| 6.1 Statusline..............................|csv-stl| 6.2 Slow CSV plugin.........................|csv-slow| @@ -204,12 +208,13 @@ If you want to automatically highlight a column, see |csv-hicol| If you would like all columns to be visually arranged, you can use the `:ArrangeColumn` or `:CSVArrangeColumn` command: > - :[range]ArrangeColumn[!] + :[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. +column size. While the command is run, a progressbar in the statusline 'stl' +will be shown. Use the bang attribute to force recalculating the column width. This is slower, but especially if you have modified the file, this will correctly @@ -217,19 +222,48 @@ 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 is given, will use the Row, to calculate the width, else will +calculate the maximum of at least the first 10,000 rows to calculate the +width. The limit of 10,000 is set to speed up the processing and can be +overriden by setting the "b:csv_arrange_use_all_rows" variable (see below). + If [range] is not given, it defaults to the current line. -By default, the columns will be righ-aligned. If you want them to be -left-aligned, set the buffer variable b:csv_arrange_leftalign = 1 for that -particular buffer, e.g. > +By default, the columns will be righ-aligned. If you want a different +alignment you need to specify this through the b:csv_arrange_align variable. +This is a string of flags ('r': right align, 'l': left align, 'c': center +alignment, '.': decimal alignment) where each flag defines the alignment for +a particular column (starting from left). Missing columns will be right aligned. +So this: > - :let b:csv_arrange_leftalign = 1 + :let b:csv_arrange_align = 'lc.' < +Will left-align the first column, center align the second column, decimal +align the third column and all following columns right align. (Note: decimal +aligning might slow down Vim and additionally, if the value is no decimal +number it will be right aligned). +If you change the alignment parameter, you need to use the "!" attribute, the +next time you run the |:ArrangeCol| command, otherwise for performance +reasons, it won't be considered. + 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| +|csv-slow| on how to increase performance for this command). For large files, +calculating the column width can take long and take a consierable amount of +memory. Therefore, the csv plugin will at most check 10.000 lines for the +width. Set the variable b:csv_arrange_use_all_rows to 1 to use all records: > + + :let b:csv_arrange_use_all_rows = 1 +< +(this could however in the worst case lead to a crash). + +To disable the statusline progressbar set the variable g:csv_no_progress: > + + :let g:csv_no_progress = 1 +< +This will disable the progressbar and slightly improve performance (since no +additional redraws are needed). + +Note: this command does not work for fixed width columns |csv-fixedwidth| See also |csv-arrange-autocmd| on how to have vim automaticaly arrange a CSV file upon entering it. @@ -267,11 +301,13 @@ will then delete all columns that match the pattern: > < will delete all columns where the pattern "foobar" matches. - *:CSVInitCSV* -3.8 InitCSV *InitCSV* + *:CSVInit* +3.8 CSVInit ----------- Reinitialize the Plugin. Use this, if you have changed the configuration of the plugin (see |csv-configuration| ). +If you use the bang (!) attribute, it will keep the b:delimiter configuration +variable. *:CSVHeader* 3.9 Header lines *Header_CSV* @@ -308,7 +344,16 @@ 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). +specify a count, that many columns will be visible (default: the first). Add +the bang to the count, if you only want the specific column to be visible. +> + :VHeader 2 +< +This will open a vertical split window containing the first 2 columns, while +> + :VHeader 2! +< +Opens a new vertical split window containing only the 2 second column. Note, this won't work with linebreaks in the column. Note also: this command does not work for fixed width columns |csv-fixedwidth| @@ -338,7 +383,10 @@ While this command > reverses the order based on column 3. -Instead of a column, you can give the flag 'n' to have it sort numerically. +The column number can be optionally followed by any of the flags [i], [n], +[x] and [o] for [i]gnoring case, sorting by [n]umeric, he[x]adecimal +or [o]ctal value. + When no column number is given, it will sort by the column, on which the cursor is currently. @@ -392,7 +440,7 @@ 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 +By default, Vim uses the a numerical format that uses the '.' as decimal separator while there is no thousands separator. If youre file contains the numbers in a different format, you can use the /format/ option to specify a different thousands separator or a different decimal separator. The format @@ -511,12 +559,14 @@ and pressing 'E' again, it would move directly to |aaa, bbbb,ccc ` + *csv-textobjects* 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) +iL Inner Line (visually linewise select all lines, that + has the same value at the cursor's column) Note, that the , , K and J overlap Vim's default mapping for ||, ||, |J| and |K| respectively. Therefore, this functionality has been @@ -844,6 +894,60 @@ for every match ('g' flag) and asks for confirmation ('c' flag). Substitutes in each column starting from the third each number and appends the EURO suffix to it. + +3.26 Count Values inside a Column *CountCol_CSV* +--------------------------------- +You can let Vim output the number of values inside a column using the `:CSVCountCol` +command > + + :[range]CountCol [nr] [distinct] + +This outputs the number of [distinct] values visible in the column [nr] +If [distinct] is not given, count's all values. Note, header rows and folded +rows won't be counted. + +See also |csv-aggregate-functions| + + + *MinCol_CSV* +3.27 Maximum/Minimum value of a Column *MaxCol_CSV* +--------------------------------------- +You can let Vim output the 10 maximum/minimum values of a column using the +`:CSVMaxCol` command > + + :[range]MaxCol [nr][distinct] [/format/] + :[range]MinCol [nr][distinct] [/format/] + +This outputs the result of the column within the range given. If no range +is given, this will calculate the max value 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 numerical format that uses the '.' as decimal +separator while there is no thousands separator. If youre file contains +the numbers in a different format, you can use the /format/ option to specify +a different thousands separator or a different decimal separator. The format +needs to be specified like this: + /x:y/ +where 'x' defines the thousands separator and y defines the decimal +separator and each one is optional. This means, that > + + :MaxCol 1 /:,/ + +uses the default thousands separator and ',' as the decimal separator and > + + :MaxCol 2 / :./ + +uses the Space as thousands separator and the '.' as decimal separator. + +If [distinct] is given, only returns the number of distinct values. + +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| ============================================================================== 4. CSV Configuration *csv-configuration* @@ -999,6 +1103,16 @@ own syntax highlighting like this in your |.vimrc| > hi CSVColumnHeaderEven ... hi CSVColumnHeaderOdd ... +Alternatively, you can simply link those highlighting groups to some other +ones, you really like: > + + hi link CSVColumnOdd MoreMsg + hi link CSVColumnEven Question +< +If you do not want column highlighting, set the variable +g:csv_no_column_highlight to 1 > + + :let g:csv_no_column_highlight = 1 < Note, these changes won't take effect, until you restart Vim. @@ -1207,6 +1321,19 @@ 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. +5.5 CSVCount(col, fmt, startline, endline[, distinct]) *CSVCount()* +------------------------------------------------------ +Returns the count of values for column col. If the optional parameter +[distinct] is given, only returns the distinct number of values. + +5.6 CSVMax(col, fmt, startline, endline) *CSVMax()* +------------------------------------------------------ +Returns the 10 largest values for column col. + +5.7 CSVMin(col, fmt, startline, endline) *CSVMin()* +------------------------------------------------------ +Returns the 10 smallest values for column col. + ============================================================================== 6. CSV Tips and Tricks *csv-tips* @@ -1383,12 +1510,17 @@ slow down Vim considerably. 6.5 Syntax error when opening a CSV file *csv-syntax-error* ---------------------------------------- +If you see this error: > + + CSV Syntax:Invalid column pattern, using default pattern \%([^,]*,\|$\) +< 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| +The easy way to fix it, is to make sure the :syntax on (|:syn-on|) statement +comes after the :filetype plugin (|:filetype-plugin-on|) statement in your +|.vimrc| Alternatively, you can simply call |InitCSV| and ignore the error. @@ -1415,7 +1547,77 @@ Index;Value1;Value2~ ============================================================================== 7. CSV Changelog *csv-changelog* -0.31 Jan 15, 2015 "{{{1 +0.32 (unreleased) {{{1 +- Remove old Vim 7.3 workarounds (plugin needs now a Vim version 7.4) +- allow to align columns differently (right/left or center align) for + |ArrangeColumn_CSV| (suggested by Giorgio Robino, thanks!) +- document better how to adjust syntax highlighting (suggested by Giorgio + Robino, thanks!) +- Allow the |:CSVHeader| command to only display a specific column (suggested + by Giorgio Robino, thanks!) +- When using |VHeader_CSV| or |Header_CSV| command, check + number/relativenumber and foldcolumn to make sure, header line is always + aligened with main window (suggested by Giorgio Robino, thanks!) +- hide search pattern, when calling |SearchInColumn_CSV| (suggested by Giorgio + Robino, thanks!) +- compute correct width of marginline for |:CSVTable| +- do not allow |:CSVTable| command for csv files, that's what the + |:CSVTabularize| command is for. +- add progressbar for the |:CSVArrangeCol| command. +- |InitCSV| accepts a '!' for keeping the b:delimiter (|csv-delimiter|) variable + (https://github.com/chrisbra/csv.vim/issues/43 reported by Jeet Sukumaran, + thanks!) +- New text-object iL (Inner Line, to visually select the lines that have the + same value in the cursor column, as requested at + https://github.com/chrisbra/csv.vim/issues/44, thanks justmytwospence!) +- |:CSVArrangeColumn| can be given an optional row number and the width will + be calculated using that row. (https://github.com/chrisbra/csv.vim/issues/45 + reported by jchain, thanks!) +- Allow for hexadecimal |Sort_CSV| + (https://github.com/chrisbra/csv.vim/issues/46, reported by ThomsonTan, + thanks!) +- support all flags for |Sort_CSV| as for the builting |:sort| command (except + for "u" and "r") +- prevent mapping of and in visual mode (reported by naught101 at + https://github.com/chrisbra/csv.vim/issues/50, thanks!) +- prevent increasing column width on subsequent call of |:ArrangeColumn_CSV| + (reported by naught101 at https://github.com/chrisbra/csv.vim/issues/51, + thanks!) +- New Count function |CSVCount()| (reported by jungle-booke at + https://github.com/chrisbra/csv.vim/issues/49, thanks!) +- fix pattern generation for last column +- |ConvertData_CSV| should filter out folded lines (reported by jungle-booke + at https://github.com/chrisbra/csv.vim/issues/53, thanks!) +- Make |:CSVTable| ignore folded lines (reported by jungle-booke at + https://github.com/chrisbra/csv.vim/issues/56, thanks!) +- Better filtering for dynamic filters (reported by jungle-booke at + https://github.com/chrisbra/csv.vim/issues/57, thanks!) +- Implement a |MaxCol_CSV| and |MinCol_CSV| command (reported by jungle-booke at + https://github.com/chrisbra/csv.vim/issues/60, thanks!) +- Make |UnArrangeColumn_CSV| strip leading and trailing whitespace (reported + by SuperFluffy at https://github.com/chrisbra/csv.vim/issues/62, thanks!) +- Do not sort headerlines (reported by jungle-booke at https://github.com/chrisbra/csv.vim/issues/63, + thanks!) +- Do not error out in |:ArrangeCol| command, if line does not have that many + columns (reported by SuperFluffy at https://github.com/chrisbra/csv.vim/issues/64, thanks) +- Use |OptionSet|autocommand to adjust window for |CSV_Header| command +- when doing |:ArrangeCol| with bang attribute, unarrange first, so that if + the alignment changed, it will be adjusted accordingly +- Allow distinct keyword for |MaxCol_CSV| and |MinCol_CSV| command (reported + by jungle-boogie at https://github.com/chrisbra/csv.vim/issues/67, thanks!) +- When left-aligning columns, don't add trailing whitespace (reported by + jjaderberg at https://github.com/chrisbra/csv.vim/issues/66, thanks!) +- Do not remove highlighting when calling ":CSVTabularize" (reported by + hyiltiz at https://github.com/chrisbra/csv.vim/issues/70, thanks!) +- Make |:ArrangeCol| respect given headerlines +- when checking Header/comment lines at beginning of file, make sure to escape + the comment pattern correctly. +- use b:csv_headerline variable for checking column name and column numbers + (reported by Werner Freund at https://github.com/chrisbra/csv.vim/issues/78, + thanks!) + +0.31 Jan 15, 2015 {{{1 +- supports for Vim 7.3 dropped - 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) diff --git a/vimfiles/ftplugin/csv.vim b/vimfiles/ftplugin/csv.vim index 0adb3a1..797d62e 100644 --- a/vimfiles/ftplugin/csv.vim +++ b/vimfiles/ftplugin/csv.vim @@ -10,13 +10,26 @@ " Some ideas are taken from the wiki http://vim.wikia.com/wiki/VimTip667 " though, implementation differs. -" Plugin folklore "{{{2 +" Plugin folklore "{{{1 +if v:version < 700 || exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim fu! DetermineSID() let s:SID = matchstr(expand(''), '\zs\d\+\ze_DetermineSID$') endfu call s:DetermineSID() delf s:DetermineSID - +let s:csv_numeric_sort = v:version > 704 || v:version == 704 && has("patch341") +if !s:csv_numeric_sort "{{{2 + fu! CSVSortValues(i1, i2) "{{{3 + return (a:i1+0) == (a:i2+0) ? 0 : (a:i1+0) > (a:i2+0) ? 1 : -1 + endfu +endif +" Function definitions: "{{{1 fu! CSVArrangeCol(first, last, bang, limit) range "{{{2 if &ft =~? 'csv' call ArrangeCol(a:first, a:last, a:bang, a:limit) @@ -25,15 +38,6 @@ fu! CSVArrangeCol(first, last, bang, limit) range "{{{2 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 @@ -41,7 +45,9 @@ fu! Warn(mess) "{{{3 echohl Normal endfu -fu! Init(startline, endline) "{{{3 +fu! 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 @@ -56,10 +62,12 @@ fu! Init(startline, endline) "{{{3 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 + if !keep + if !exists("g:csv_delim") + let b:delimiter=GetDelimiter(a:startline, a:endline) + else + let b:delimiter=g:csv_delim + endif endif " Define custom commentstring @@ -140,7 +148,9 @@ fu! Init(startline, endline) "{{{3 " enable CSV Menu call Menu(1) call DisableFolding() - silent do Syntax + if !exists("b:current_syntax") + silent do Syntax + endif unlet! b:csv_start b:csv_end " Remove configuration variables @@ -156,33 +166,6 @@ fu! Init(startline, endline) "{{{3 " 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 @@ -194,11 +177,8 @@ fu! LocalSettings(type) "{{{3 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". + let b:browsefilter="CSV Files (*.csv, *.dat)\t*.csv;*.dat\n". \ "All Files\t*.*\n" - endif if has("conceal") setl cole=2 cocu=nc @@ -231,20 +211,20 @@ fu! LocalSettings(type) "{{{3 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 + " Highlight column, on which the cursor is + if exists("g:csv_highlight_column") && g:csv_highlight_column =~? 'y' + exe "aug CSV_HI".bufnr('') au! - au CursorMoved HiColumn + exe "au CursorMoved HiColumn" + exe "au BufWinLeave HiColumn!" aug end " Set highlighting for column, on which the cursor is currently HiColumn - elseif exists("#CSV_HI#CursorMoved") - aug CSV_HI - au! CursorMoved + else + exe "aug CSV_HI".bufnr('') + exe "au! CursorMoved " aug end - aug! CSV_HI + exe "aug! CSV_HI".bufnr('') " Remove any existing highlighting HiColumn! endif @@ -262,15 +242,14 @@ fu! DoAutoCommands() "{{{3 augroup END endif endfu - -fu! GetPat(colnr, maxcolnr, pat) "{{{3 +fu! GetPat(colnr, maxcolnr, pat, allowmore) "{{{3 + " if a:allowmmore, allows more to match after the pattern if a:colnr > 1 && a:colnr < a:maxcolnr if !exists("b:csv_fixed_width_cols") return '^' . GetColPat(a:colnr-1,0) . '\%([^' . \ b:delimiter . ']\{-}\)\?\zs' . a:pat . '\ze' . - \ '\%([^' . b:delimiter .']\{-}\)\?' . - \ b:delimiter . GetColPat(a:maxcolnr - a:colnr, 0) . - \ '$' + \ (a:allowmore ? ('\%([^' . 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\%' @@ -279,24 +258,22 @@ fu! GetPat(colnr, maxcolnr, pat) "{{{3 elseif a:colnr == a:maxcolnr if !exists("b:csv_fixed_width_cols") return '^' . GetColPat(a:colnr - 1,0) . - \ '\%([^' . b:delimiter . - \ ']\{-}\)\?\zs' . a:pat . '\ze' + \ '\zs' . a:pat . '\ze' . (a:allowmore ? '' : '$') else return '\%' . b:csv_fixed_width_cols[-1] . - \ 'c\zs' . a:pat . '\ze' + \ 'c\zs' . a:pat . '\ze' . (a:allowmore ? '' : '$') 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) . '$' + \ (a:allowmore ? ('\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) @@ -339,9 +316,10 @@ fu! SearchColumn(arg) "{{{3 call Warn("There exists no column " . colnr) return 1 endif - let @/ = GetPat(colnr, maxcolnr, '\%('.pat. '\)') + let @/ = GetPat(colnr, maxcolnr, '\%('.pat. '\)', 1) try - norm! n + " force redraw, so that the search pattern isn't shown + exe "norm! n\" catch /^Vim\%((\a\+)\)\=:E486/ " Pattern not found echohl Error @@ -352,8 +330,6 @@ fu! SearchColumn(arg) "{{{3 echohl Normal endtry endfu - - fu! DeleteColumn(arg) "{{{3 let _wsv = winsaveview() if a:arg =~ '^[/]' @@ -376,7 +352,6 @@ fu! DeleteColumn(arg) "{{{3 endif call winrestview(_wsv) endfu - fu! DelColumn(colnr) "{{{3 let maxcolnr = MaxColumns() let _p = getpos('.') @@ -414,13 +389,12 @@ fu! DelColumn(colnr) "{{{3 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 a:colnr > MaxColumns() + call Warn("There exists no column " . a:colnr) + return + endif if empty(a:colnr) let colnr=WColumn() else @@ -453,7 +427,6 @@ fu! HiCol(colnr, bang) "{{{3 exe ":2match " . s:hiGroup . ' /' . pat . '/' endif endfu - fu! GetDelimiter(first, last) "{{{3 if !exists("b:csv_fixed_width_cols") let _cur = getpos('.') @@ -487,7 +460,6 @@ fu! GetDelimiter(first, last) "{{{3 return '' endif endfu - fu! WColumn(...) "{{{3 " Return on which column the cursor is let _cur = getpos('.') @@ -507,7 +479,7 @@ fu! WColumn(...) "{{{3 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') + let head = split(get(b:, 'csv_headerline', 1),b:col.'\zs') " remove preceeding whitespace if len(head) < ret call Warn("Header has no field ". ret) @@ -521,7 +493,7 @@ fu! WColumn(...) "{{{3 let temp=getpos('.')[2] let j=1 let ret = 1 - for i in sort(b:csv_fixed_width_cols, "SortList") + for i in sort(b:csv_fixed_width_cols, s:csv_numeric_sort ? 'n' : 's:CSVSortValues') if temp >= i let ret = j endif @@ -531,22 +503,13 @@ fu! WColumn(...) "{{{3 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 + let this_col = exists("a:1") "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 + let i = this_col ? a:1 : get(b:, 'csv_headerline', 1) while 1 - let l = getline(i, i+10) + let l = getline(i, (this_col ? i : i+10)) " Filter comments out let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\') @@ -572,8 +535,9 @@ fu! MaxColumns(...) "{{{3 return len(b:csv_fixed_width_cols) endif endfu - -fu! ColWidth(colnr) "{{{3 +fu! ColWidth(colnr, ...) "{{{3 + " if a:1 is given, specifies the row, for which to calculate the width + " " Return the width of a column " Internal function let width=20 "Fallback (wild guess) @@ -581,7 +545,18 @@ fu! ColWidth(colnr) "{{{3 if !exists("b:csv_fixed_width_cols") if !exists("b:csv_list") - let b:csv_list=getline(1,'$') + " only check first 10000 lines, to be faster + let last = line('$') + if exists("a:1") + let last = a:1 + endif + if !get(b:, 'csv_arrange_use_all_rows', 0) + if last > 10000 + let last = 10000 + call 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)') @@ -589,13 +564,9 @@ fu! ColWidth(colnr) "{{{3 endif try for item in b:csv_list - call add(tlist, item[a:colnr-1]) + call add(tlist, get(item, a:colnr-1, '')) endfor - " we have a list of the first 10 rows - " Now transform it to a list of field a:colnr - " and then return the maximum strlen - " That could be done in 1 line, but that would look ugly - "call map(list, 'split(v:val, b:col."\\zs")[a:colnr-1]') + " do not strip leading whitespace call map(tlist, 'substitute(v:val, ".", "x", "g")') call map(tlist, 'strlen(v:val)') return max(tlist) @@ -617,8 +588,9 @@ fu! ColWidth(colnr) "{{{3 endif endif endfu - -fu! ArrangeCol(first, last, bang, limit) range "{{{3 +fu! ArrangeCol(first, last, bang, limit, ...) range "{{{3 + " a:1, optional width parameter of line from which to take the width + " " explicitly give the range as argument to the function if exists("b:csv_fixed_width_cols") " Nothing to do @@ -626,18 +598,42 @@ fu! ArrangeCol(first, last, bang, limit) range "{{{3 return endif let cur=winsaveview() - if a:bang - if a:bang - " Force recalculating the Column width - unlet! b:csv_list b:col_width + if a:bang || (exists("a:1") && !empty(a:1)) + if a:bang && exists("b:col_width") + " Unarrange, so that if csv_arrange_align has changed + " it will be adjusted automaticaly + call PrepUnArrangeCol(a:first, a:last) endif + " Force recalculating the Column width + unlet! b:csv_list b:col_width elseif a:limit > -1 && a:limit < getfsize(fnamemodify(bufname(''), ':p')) return endif + let first = a:first + let last = a:last + if exists("b:csv_headerline") + if a:first < b:csv_headerline + let first = b:csv_headerline + endif + if a:last < b:csv_headerline + let last = b:csv_headerline + endif + endif + if first > line('$') + let first=line('$') + endif + if last > line('$') + let last=line('$') + endif + if &vbs + echomsg printf("ArrangeCol Start: %d, End: %d", first, last) + endif + if !exists("b:col_width") " Force recalculation of Column width - call CalculateColumnWidth() + let row = exists("a:1") ? a:1 : '' + call CalculateColumnWidth(row) endif if &ro @@ -648,17 +644,40 @@ fu! ArrangeCol(first, last, bang, limit) range "{{{3 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) + let s:count = 0 + let _stl = &stl + let s:max = (last - first + 1) * len(b:col_width) + let s:temp = 0 + try + exe "sil". first . ',' . last .'s/' . (b:col) . + \ '/\=Columnize(submatch(0))/' . (&gd ? '' : 'g') + finally + " Clean up variables, that were only needed for 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! ProgressBar(cnt, max) "{{{3 + if get(g:, 'csv_no_progress', 0) || a:max == 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! PrepUnArrangeCol(first, last) "{{{3 " Because of the way, Vim works with " a:firstline and a:lastline parameter, @@ -680,26 +699,27 @@ fu! PrepUnArrangeCol(first, last) "{{{3 " 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', '', '') + return substitute(a:match, '\%(^\s\+\)\|\%(\s\+\ze'.b:delimiter. '\?$\)', '', 'g') endfu - -fu! CalculateColumnWidth() "{{{3 +fu! CalculateColumnWidth(row) "{{{3 " Internal function, not called from external, " does not work with fixed width columns let b:col_width=[] try + if exists("b:csv_headerline") + if line('.') < b:csv_headerline + call cursor(b:csv_headerline,1) + endif + endif let s:max_cols=MaxColumns(line('.')) for i in range(1,s:max_cols) - call add(b:col_width, ColWidth(i)) + if empty(a:row) + call add(b:col_width, ColWidth(i)) + else + call add(b:col_width, ColWidth(i,a:row)) + endif endfor catch /csv:no_col/ call Warn("Error: getting Column numbers, aborting!") @@ -708,9 +728,8 @@ fu! CalculateColumnWidth() "{{{3 endtry " delete buffer content in variable b:csv_list, " this was only necessary for calculating the max width - unlet! b:csv_list + unlet! b:csv_list s:columnize_count s:decimal_column endfu - fu! Columnize(field) "{{{3 " Internal function, not called from external, " does not work with fixed width columns @@ -725,6 +744,7 @@ fu! Columnize(field) "{{{3 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, @@ -732,54 +752,86 @@ fu! Columnize(field) "{{{3 " 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 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 ProgressBar(s:count,s:max) 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) + 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 - 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) + if get(s:decimal_column, colnr, 0) == 0 + call CheckHeaderLine() + call NumberFormat() + let data = 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! GetColPat(colnr, zs_flag) "{{{3 " Return Pattern for given column if a:colnr > 1 @@ -804,17 +856,26 @@ fu! GetColPat(colnr, zs_flag) "{{{3 endif return pat . (a:zs_flag ? '\zs' : '') endfu - -fu! SetupQuitPre(window) "{{{3 +fu! SetupAutoCmd(window,bufnr) "{{{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 + aug CSV_QuitPre + au! + exe "au QuitPre * call CSV_CloseBuffer(".winbufnr(a:window).")" + if !exists("##OptionSet") + exe "au CursorHold call CSV_SetSplitOptions(".a:window.")" + else + exe "au OptionSet foldcolumn,number,relativenumber call CSV_SetOption(".a:bufnr. + \ ", ".bufnr('%').", expand(''), v:option_new)" + endif + exe "au VimResized,FocusLost,FocusGained call CSV_SetSplitOptions(".a:window.")" + aug END +endfu +fu! CSV_SetOption(csvfile, header, option, value) "{{{3 + " only trigger if the option is called in the correct buffer + if getbufvar(a:csvfile, 'csv_SplitWindow') && bufnr('') == a:csvfile + call setbufvar(a:header, '&'.a:option, a:value) + 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!") @@ -833,8 +894,9 @@ fu! SplitHeaderLine(lines, bang, hor) "{{{3 let _sbo = &sbo let a = [] let b=b:col + let bufnr = bufnr('.') if a:hor - setl scrollopt=hor scrollbind + 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) @@ -847,16 +909,21 @@ fu! SplitHeaderLine(lines, bang, hor) "{{{3 "setl syntax=csv sil! doautocmd FileType csv noa 1 + sil! sign unplace * exe "resize" . lines - setl scrollopt=hor winfixheight nowrap - "let &l:stl=repeat(' ', winwidth(0)) + 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 + setl scrollopt=ver scrollbind cursorbind noa 0 - let a=CopyCol('',1,a:lines) + if a:lines[-1:] is? '!' + let a=CopyCol('',a:lines,'') + else + let a=CopyCol('',1, a:lines-1) + endif " Does it make sense to use the preview window? "vert sil! pedit |wincmd w | enew! above vsp +enew @@ -872,11 +939,14 @@ fu! SplitHeaderLine(lines, bang, hor) "{{{3 noa 0 let b:csv_SplitWindow = winnr() sil :call 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 + setl scrollopt=ver winfixwidth cursorbind nonu nornu fdc=0 endif - call SetupQuitPre(winnr()) + call SetupAutoCmd(winnr(),bufnr) + " disable airline + let w:airline_disabled = 1 let win = winnr() setl scrollbind buftype=nowrite bufhidden=wipe noswapfile nobuflisted noa wincmd p @@ -890,29 +960,33 @@ fu! SplitHeaderLine(lines, bang, hor) "{{{3 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 + let winnr = winnr() + if winnr == b:csv_SplitWindow || winbufnr(b:csv_SplitWindow) == bufnr('') + " window already closed + 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 + call CSV_CloseBuffer(bufnr('%')) catch /^Vim\%((\a\+)\)\=:E444/ " cannot close last window catch /^Vim\%((\a\+)\)\=:E517/ " buffer already wiped " no-op + finally + unlet! b:csv_SplitWindow + aug CSV_Preview + au! + aug END + aug! CSV_Preview 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) @@ -920,7 +994,6 @@ fu! SplitHeaderToggle(hor) "{{{3 :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 @@ -956,7 +1029,7 @@ fu! MoveCol(forward, line, ...) "{{{3 let line=line('$') endif if foldclosed(line) != -1 - let line = line > line('.') ? foldclosedend(line) : foldclosed(line) + let line = line > line('.') ? foldclosedend(line) + 1 : foldclosed(line) endif " Generate search pattern @@ -1042,23 +1115,23 @@ fu! MoveCol(forward, line, ...) "{{{3 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 + " :Sort command + let wsv = winsaveview() + let flag = matchstr(a:colnr, '[nixo]') + call CheckHeaderLine() + let line1 = a:line1 + let line2 = a:line2 + if line1 <= s:csv_fold_headerline + let line1 += s:csv_fold_headerline endif - let col = (empty(a:colnr) || a:colnr !~? '\d\+') ? WColumn() : a:colnr+0 + if line2 <= s:csv_fold_headerline + let line2 += s:csv_fold_headerline + endif + let col = (empty(a:colnr) || a:colnr !~? '\d\+[nixo]\?') ? WColumn() : a:colnr+0 if col != 1 if !exists("b:csv_fixed_width_cols") let pat= '^' . GetColPat(col-1,1) . b:col @@ -1068,11 +1141,10 @@ fu! Sort(bang, line1, line2, colnr) range "{{{3 else let pat= '^' . GetColPat(col,0) endif - exe a:line1. ','. a:line2. "sort". (a:bang ? '!' : '') . - \' r ' . (numeric ? 'n' : '') . ' /' . pat . '/' + exe line1. ','. line2. "sort". (a:bang ? '!' : '') . + \' r'. flag. ' /' . 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 @@ -1119,7 +1191,6 @@ fu! CopyCol(reg, col, cnt) "{{{3 return a endif endfu - fu! MoveColumn(start, stop, ...) range "{{{3 " Move column behind dest " Explicitly give the range as argument, @@ -1175,11 +1246,8 @@ fu! MoveColumn(start, stop, ...) range "{{{3 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, @@ -1246,10 +1314,8 @@ fu! AddColumn(start, stop, ...) range "{{{3 " 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 @@ -1286,7 +1352,65 @@ fu! SumColumn(list) "{{{3 return sum endif endfu - +fu! MaxColumn(list) "{{{3 + " Sum a list of values, but only consider the digits within each value + " parses the digits according to the given format (if none has been + " specified, assume POSIX format (without thousand separator) If Vim has + " does not support floats, simply sum up only the integer part + if empty(a:list) + return 0 + else + let result = [] + for item in a:list + if empty(item) + continue + endif + let nr = matchstr(item, '-\?\d\(.*\d\)\?$') + let format1 = '^-\?\d\+\zs\V' . s:nr_format[0] . '\m\ze\d' + let format2 = '\d\+\zs\V' . s:nr_format[1] . '\m\ze\d' + try + let nr = substitute(nr, format1, '', '') + if has("float") && s:nr_format[1] != '.' + let nr = substitute(nr, format2, '.', '') + endif + catch + let nr = 0 + endtry + call add(result, has("float") ? str2float(nr) : nr+0) + endfor + let result = sort(result, s:csv_numeric_sort ? 'n' : 's:CSVSortValues') + let ind = len(result) > 9 ? 9 : len(result) + if has_key(get(s:, 'additional', {}), 'distinct') && s:additional['distinct'] + if exists("*uniq") + let result=uniq(result) + else + let l = {} + for item in result + let l[item] = get(l, 'item', 0) + endfor + let result = keys(l) + endif + endif + return s:additional.ismax ? reverse(result)[:ind] : result[:ind] + endif +endfu +fu! CountColumn(list) "{{{3 + if empty(a:list) + return 0 + elseif has_key(get(s:, 'additional', {}), 'distinct') && s:additional['distinct'] + if exists("*uniq") + return len(uniq(sort(a:list))) + else + let l = {} + for item in a:list + let l[item] = get(l, 'item', 0) + 1 + endfor + return len(keys(l)) + endif + else + return len(a:list) + endif +endfu fu! DoForEachColumn(start, stop, bang) range "{{{3 " Do something for each column, " e.g. generate SQL-Statements, convert to HTML, @@ -1309,6 +1433,10 @@ fu! DoForEachColumn(start, stop, bang) range "{{{3 endif for item in range(a:start, a:stop, 1) + if foldlevel(line) + " Filter out folded lines (from dynamic filter) + continue + endif let t = g:csv_convert let line = getline(item) if line =~ '^\s*\V'. escape(b:csv_cmt[0], '\\') @@ -1350,9 +1478,7 @@ fu! DoForEachColumn(start, stop, bang) range "{{{3 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) @@ -1365,7 +1491,6 @@ endfun fu! EscapeValue(val) "{{{3 return '\V' . escape(a:val, '\') endfu - fu! FoldValue(lnum, filter) "{{{3 call CheckHeaderLine() @@ -1386,7 +1511,6 @@ fu! FoldValue(lnum, filter) "{{{3 endfor return (result > 0) endfu - fu! PrepareFolding(add, match) "{{{3 if !has("folding") return @@ -1426,41 +1550,14 @@ fu! PrepareFolding(add, match) "{{{3 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 + let a = ProcessFieldValue(a) + let pat = '\%(^\|'.b:delimiter. '\)\@<='.EscapeValue(a). + \ '\m\ze\%('.b:delimiter.'\|$\)' " Make a column pattern let b= '\%(' . \ (exists("b:csv_fixed_width") ? '.*' : '') . - \ GetPat(col, max, EscapeValue(a) . '\m') . - \ '\)' + \ GetPat(col, max, pat, 0) . '\)' let s:filter_count += 1 let b:csv_filter[s:filter_count] = { 'pat': b, 'id': s:filter_count, @@ -1488,7 +1585,38 @@ fu! PrepareFolding(add, match) "{{{3 endif call winrestview(cpos) endfu +fu! ProcessFieldValue(field) "{{{3 + let a = a:field + if !exists("b:csv_fixed_width") + try + " strip leading whitespace + if (a =~ '\s\+'. b:delimiter . '$') + let b = split(a, '^\s\+\ze[^' . b:delimiter. ']\+')[0] + else + let b = a + endif + catch /^Vim\%((\a\+)\)\=:E684/ + " empty pattern - should match only empty columns + let b = a + endtry + " strip trailing delimiter + try + let a = split(b, b:delimiter . '$')[0] + catch /^Vim\%((\a\+)\)\=:E684/ + let a = b + endtry + + if a == b:delimiter + try + let a=repeat(' ', ColWidth(col)) + catch + " no-op + endtry + endif + endif + return a +endfu fu! OutputFilters(bang) "{{{3 if !a:bang call CheckHeaderLine() @@ -1529,12 +1657,10 @@ fu! OutputFilters(bang) "{{{3 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) @@ -1555,7 +1681,6 @@ fu! GetColumn(line, col) "{{{3 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 @@ -1563,14 +1688,12 @@ fu! RemoveLastItem(count) "{{{3 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") @@ -1580,7 +1703,6 @@ fu! NumberFormat() "{{{3 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 @@ -1588,7 +1710,6 @@ fu! CheckHeaderLine() "{{{3 let s:csv_fold_headerline = b:csv_headerline endif endfu - fu! AnalyzeColumn(...) "{{{3 let maxcolnr = MaxColumns() if len(a:000) == 1 @@ -1617,10 +1738,12 @@ fu! AnalyzeColumn(...) "{{{3 let res[item]+=1 endfor - let max_items = reverse(sort(values(res))) + let max_items = reverse(sort(values(res), s:csv_numeric_sort ? 'n' : 's:CSVSortValues')) + " What about the minimum 5 items? let count_items = keys(res) if len(max_items) > 5 call remove(max_items, 5, -1) + call map(max_items, 'printf(''\V%s\m'', escape(v:val, ''\\''))') call filter(res, 'v:val =~ ''^''.join(max_items, ''\|'').''$''') endif @@ -1637,7 +1760,7 @@ fu! AnalyzeColumn(...) "{{{3 let i=1 for val in max_items for key in keys(res) - if res[key] == val && i <= len(max_items) + if res[key] =~ val && i <= len(max_items) if !empty(b:delimiter) let k = substitute(key, b:delimiter . '\?$', '', '') else @@ -1660,7 +1783,6 @@ fu! AnalyzeColumn(...) "{{{3 echo printf("different values: %d", len(count_items)) unlet max_items endfunc - fu! Vertfold(bang, col) "{{{3 if a:bang do Syntax @@ -1679,7 +1801,7 @@ fu! Vertfold(bang, col) "{{{3 else let colnr=a:col endif - let pat=GetPat(colnr, MaxColumns(), '.*') + let pat=GetPat(colnr, MaxColumns(), '.*', 1) if exists("b:csv_fixed_width_cols") && \ pat !~ '^\^\.\*' " Make the pattern implicitly start at line start, @@ -1691,26 +1813,26 @@ fu! Vertfold(bang, col) "{{{3 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") + call Warn("Command disabled: 'colorcolumn' option not available") return endif " Turn off syntax highlighting syn clear - let max_len = len(split(getline(1), '\zs')) + let max_line = line('$') > 10 ? 10 : line('$') + let t = getline(1, max_line) + let max_len = max(map(t, 'len(split(v:val, ''\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 ", , , , ..." + echo ", , , , , ?" let char=getchar() while 1 + let skip_mess = 0 if char == "\" || char == "\" let tcc = eval('tcc'.(char=="\" ? '-' : '+').'1') if tcc < 0 @@ -1733,24 +1855,39 @@ fu! InitCSVFixedWidth() "{{{3 elseif char == "\" || char == "\n" || char == "\r" " Enter let Dict[tcc] = 1 break + elseif char == char2nr('?') + redraw! + echohl Title + echo "Key\tFunction" + echo "==================" + echohl Normal + echo "\tDefine new column" + echo "\tMove left" + echo "\tMove right" + echo "\tAbort" + echo "\tDelete last column definition" + echo "?\tShow this help\n" + let skip_mess = 1 else + let Dict={} break endif let &l:cc=tcc . (!empty(keys(Dict))? ',' . join(keys(Dict), ','):'') - redraw! - echo ", , , , ..." + if !skip_mess + redraw! + echo ", , , , ..." + endif 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"), ',') + let b:csv_fixed_width_cols = sort(keys(Dict), s:csv_numeric_sort ? 'n' : 's:CSVSortValues') + let b:csv_fixed_width = join(sort(keys(Dict), s:csv_numeric_sort ? 'n' : 's:CSVSortValues'), ',') 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") @@ -1785,7 +1922,6 @@ fu! NewRecord(line1, line2, count) "{{{3 call append(nr, line) endfor endfu - fu! MoveOver(outer) "{{{3 " Move over a field " a:outer means include the delimiter @@ -1821,7 +1957,6 @@ fu! MoveOver(outer) "{{{3 call winrestview(_wsv) let @/ = _s endfu - fu! CSVMappings() "{{{3 call Map('noremap', 'W', ':call MoveCol(1, line("."))') call Map('noremap', '', ':call MoveCol(1, line("."))') @@ -1830,30 +1965,31 @@ fu! CSVMappings() "{{{3 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('nnoremap', '', ':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 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('xnoremap', 'if', ':call MoveOver(0)') + call Map('xnoremap', 'af', ':call MoveOver(1)') call Map('omap', 'af', ':norm vaf') call Map('omap', 'if', ':norm vif') + call Map('xnoremap', 'iL', ':call SameFieldRegion()') + call Map('omap', 'iL', ':call SameFieldRegion()') " 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('xnoremap', 'W', 'W') + call Map('xnoremap', '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') @@ -1865,12 +2001,13 @@ fu! CommandDefinitions() "{{{3 call LocalCmd("DeleteColumn", ':call DeleteColumn()', \ '-nargs=? -complete=custom,SortComplete') call LocalCmd("ArrangeColumn", - \ ':call ArrangeCol(, , 0, -1)', - \ '-range -bang') + \ ':call ArrangeCol(, , 0, -1, )', + \ '-range -bang -nargs=?') call LocalCmd("UnArrangeColumn", \':call PrepUnArrangeCol(, )', \ '-range') - call LocalCmd("InitCSV", ':call Init(,)', '-range=%') + call LocalCmd("CSVInit", ':call Init(,,0)', + \ '-bang -range=%') call LocalCmd('Header', \ ':call SplitHeaderLine(,0,1)', \ '-nargs=? -bang') @@ -1893,6 +2030,15 @@ fu! CommandDefinitions() "{{{3 call LocalCmd("SumCol", \ ':echo csv#EvalColumn(, "SumColumn", ,)', \ '-nargs=? -range=% -complete=custom,SortComplete') + call LocalCmd("MaxCol", + \ ':echo csv#EvalColumn(, "MaxColumn", ,, 1)', + \ '-nargs=? -range=% -complete=custom,SortComplete') + call LocalCmd("MinCol", + \ ':echo csv#EvalColumn(, "MaxColumn", ,, 0)', + \ '-nargs=? -range=% -complete=custom,SortComplete') + call LocalCmd("CountCol", + \ ':echo csv#EvalColumn(, "CountColumn", ,)', + \ '-nargs=? -range=% -complete=custom,SortComplete') call LocalCmd("ConvertData", \ ':call PrepareDoForEachColumn(,,0)', \ '-bang -nargs=? -range=%') @@ -1919,7 +2065,6 @@ fu! CommandDefinitions() "{{{3 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' ? '' : '') @@ -1937,11 +2082,12 @@ fu! Map(map, name, definition, ...) "{{{3 let unmap = 'ounmap' elseif a:map == 'imap' let unmap = 'iunmap' + elseif a:map == 'xnoremap' + let unmap = 'xunmap' 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 @@ -1952,11 +2098,10 @@ fu! LocalCmd(name, definition, args) "{{{3 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.&Init\ Plugin :CSVInit amenu CSV.SetUp\ &fixedwidth\ Cols :CSVFixed amenu CSV.-sep1- amenu &CSV.&Column.&Number :WhatColumn @@ -1983,7 +2128,6 @@ fu! Menu(enable) "{{{3 sil! amenu disable CSV endif endfu - fu! SaveOptions(list) "{{{3 let save = {} for item in a:list @@ -1991,7 +2135,6 @@ fu! SaveOptions(list) "{{{3 endfor return save endfu - fu! NewDelimiter(newdelimiter, firstl, lastl) "{{{3 let save = SaveOptions(['ro', 'ma']) if exists("b:csv_fixed_width_cols") @@ -2039,7 +2182,6 @@ fu! NewDelimiter(newdelimiter, firstl, lastl) "{{{3 endif unlet! _delim endfu - fu! IN(list, value) "{{{3 for item in a:list if item == a:value @@ -2048,7 +2190,6 @@ fu! IN(list, value) "{{{3 endfor return 0 endfu - fu! DuplicateRows(columnlist) "{{{3 let duplicates = {} let cnt = 0 @@ -2087,7 +2228,6 @@ 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' @@ -2097,7 +2237,6 @@ fu! CheckDuplicates(list) "{{{3 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 @@ -2175,23 +2314,20 @@ fu! Transpose(line1, line2) "{{{3 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 + try + let cols = empty(a:bang) ? MaxColumns() : MaxColumns(line('.')) + catch + " No column or comment line + call Warn("No valid CSV Column!") + endtry echo cols endfu - fu! Tabularize(bang, first, last) "{{{3 + if match(split(&ft, '\.'),'csv') == -1 + call Warn("No CSV filetype, aborting!") + return + endif let _c = winsaveview() " Table delimiter definition "{{{4 if !exists("s:td") @@ -2225,8 +2361,14 @@ fu! Tabularize(bang, first, last) "{{{3 let b:csv_arrange_leftalign = 1 endif let newlines=[] + let content=[] while line <= a:last + if foldclosed(line) != -1 + let line = foldclosedend(line) + 1 + continue + endif let curline = getline(line) + call add(content, curline) if empty(split(curline, b:delimiter)) " only empty delimiters, add one empty delimiter " (:NewDelimiter strips trailing delimiter @@ -2237,6 +2379,13 @@ fu! Tabularize(bang, first, last) "{{{3 let line+=1 endw unlet! line + let delim=b:delimiter + new + call setline(1,content) + let b:delimiter=delim + let csv_highlight_column = get(g:, 'csv_highlight_column', '') + unlet! g:csv_highlight_column + call Init(1,line('$'), 1) if exists("b:csv_fixed_width_cols") let cols=copy(b:csv_fixed_width_cols) let pat = join(map(cols, ' ''\(\%''. v:val. ''c\)'' '), '\|') @@ -2252,7 +2401,7 @@ fu! Tabularize(bang, first, last) "{{{3 else " don't clear column width variable, might have been set in the " plugin! - sil call ArrangeCol(a:first, a:last, 0, -1) + sil call ArrangeCol(1, line('$'), 0, -1) if !get(b:, 'csv_arrange_leftalign',0) for line in newlines let cline = getline(line) @@ -2267,51 +2416,52 @@ fu! Tabularize(bang, first, last) "{{{3 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) + 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 NewDelimiter(s:td.vbar, a:first, a:last+adjust_last) + call NewDelimiter(s:td.vbar, 1, line('$')) "exe printf('sil %d,%ds/%s/%s/ge', a:first, (a:last+adjust_last), " \ (exists("b:csv_fixed_width_cols") ? pat : b:delimiter ), s:td.vbar) " Add vertical bar in first column, if there isn't already one - exe printf('sil %d,%ds/%s/%s/e', a:first, a:last+adjust_last, + exe printf('sil %d,%ds/%s/%s/e', 1, line('$'), \ '^[^'. s:td.vbar. s:td.scol. ']', s:td.vbar.'&') " And add a final vertical bar, if there isn't one already - exe printf('sil %d,%ds/%s/%s/e', a:first, a:last+adjust_last, + exe printf('sil %d,%ds/%s/%s/e', 1, line('$'), \ '[^'. s:td.vbar. s:td.ecol. ']$', '&'. s:td.vbar) " Make nice intersection graphs - let line = split(getline(a:first), s:td.vbar) + let line = split(getline(1), s:td.vbar) call map(line, 'substitute(v:val, ''[^''.s:td.vbar. '']'', s:td.hbar, ''g'')') " Set top and bottom margins - call append(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) + call append(0, s:td.ltop. join(line, s:td.dhor). s:td.rtop) + call append(line('$'), s:td.lbot. join(line, s:td.uhor). s:td.rbot) if s:csv_fold_headerline > 0 - call append(a:first + s:csv_fold_headerline, marginline) + call append(1 + s:csv_fold_headerline, marginline) let adjust_last += 1 endif + " Syntax will be turned off, so disable this part + " " Adjust headerline to header of new table - let b:csv_headerline = (exists('b:csv_headerline')?b:csv_headerline+2:3) - call CheckHeaderLine() + "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 + "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, + exe printf('sil %d,%ds/^%s\zs\n/&%s&/e', 1 + s:csv_fold_headerline, line('$') + adjust_last, \ '[^'.s:td.scol. '][^'.s:td.hbar.'].*', marginline) endif syn clear let &l:ma = _ma + if !empty(csv_highlight_column) + let g:csv_highlight_column = csv_highlight_column + endif 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 @@ -2371,8 +2521,22 @@ fu! SubstituteInColumn(command, line1, line2) range "{{{3 endw else for colnr in columns - let @/ = GetPat(colnr, maxcolnr, cmd[1]) + let @/ = GetPat(colnr, maxcolnr, cmd[1], 1) while search(@/) + let curpos = getpos('.') + " safety check + if (WColumn() != colnr) + break + endif + if len(split(getline('.'), '\zs')) > curpos[2] && GetCursorChar() is# b:delimiter + " Cursor is on delimiter and next char belongs to the + " next field, skip this match + norm! l + if (WColumn() != colnr) + break + endif + call setpos('.', curpos) + endif 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 @@ -2400,7 +2564,6 @@ fu! SubstituteInColumn(command, line1, line2) range "{{{3 call call('setreg', _search) endtry endfu - fu! ColumnMode() "{{{3 let mode = mode() if mode =~# 'R' @@ -2415,9 +2578,77 @@ endfu fu! Timeout(start) "{{{3 return localtime()-a:start < 2 endfu +fu! GetCursorChar() "{{{3 + let register = ['a', getreg('a'), getregtype('a')] + try + norm! v"ay + let s=getreg('a') + return s + finally + call call('setreg', register) + endtry +endfu +fu! SameFieldRegion() "{{{3 + " visually select the region, that has the same value in the cursor field + let col = WColumn() + let max = MaxColumns() + let field = GetColumn(line('.'), col) + let line = line('.') + + let limit = [line, line] + " Search upwards and downwards from the current position and find the + " limit of the current selection + while line > 1 + let line -= 1 + if GetColumn(line, col) ==# field + let limit[0] = line + else + break + endif + endw + let line = line('.') + while line > 1 && line < line('$') + let line += 1 + if GetColumn(line, col) ==# field + let limit[1] = line + else + break + endif + endw + exe printf(':norm! %dGV%dG',limit[0],limit[1]) +endfu +fu! CSV_CloseBuffer(buffer) "{{{3 + " Setup by SetupAutoCmd autocommand + try + if bufnr((a:buffer)+0) > -1 + exe a:buffer. "bw" + endif + catch /^Vim\%((\a\+)\)\=:E517/ " buffer already wiped + " no-op + finally + augroup CSV_QuitPre + au! + augroup END + augroup! CSV_QuitPre + endtry +endfu +fu! CSV_SetSplitOptions(window) "{{{3 + if exists("s:local_stl") + " local horizontal statusline + for opt in items({'&nu': &l:nu, '&rnu': &l:rnu, '&fdc': &fdc}) + if opt[1] != getwinvar(a:window, opt[0]) + call setwinvar(a:window, opt[0], opt[1]) + endif + endfor + " Check statusline (airline might change it) + if getwinvar(a:window, '&l:stl') != s:local_stl + call setwinvar(a:window, '&stl', s:local_stl) + endif + endif +endfun " Global functions "{{{2 -fu! csv#EvalColumn(nr, func, first, last) range "{{{3 +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!") @@ -2448,6 +2679,7 @@ fu! csv#EvalColumn(nr, func, first, last) range "{{{3 " parse the optional number format let format = matchstr(a:nr, '/[^/]*/') + let s:additional={} call NumberFormat() if !empty(format) try @@ -2468,6 +2700,13 @@ fu! csv#EvalColumn(nr, func, first, last) range "{{{3 endif endtry endif + let distinct = matchstr(a:nr, '\') + if !empty(distinct) + let s:additional.distinct=1 + endif + if function(a:func) is# function("\".s:SID."_MaxColumn") + let s:additional.ismax = a:1 + endif try let result=call(function(a:func), [column]) return result @@ -2482,7 +2721,6 @@ fu! csv#EvalColumn(nr, func, first, last) range "{{{3 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 @@ -2513,42 +2751,13 @@ fu! CSVPat(colnr, ...) "{{{3 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 pat = GetPat(a:colnr, MaxColumns(), a:0 ? a:1 : '.*', 1) "let pos = match(pat, '.*\\ze') + 1 " Try to set the cursor at the beginning of the pattern " does not work "call setcmdpos(pos) return pat endfu - -fu! 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 @@ -2560,6 +2769,61 @@ fu! CSVSum(col, fmt, first, last) "{{{3 endif return csv#EvalColumn(a:col, 'SumColumn', first, last) endfu +fu! CSVMax(col, fmt, first, last) "{{{3 + let first = a:first + let last = a:last + if empty(first) + let first = 1 + endif + if empty(last) + let last = line('$') + endif + return csv#EvalColumn(a:col, 'MaxColumn', first, last, 1) +endfu +fu! CSVMin(col, fmt, first, last) "{{{3 + let first = a:first + let last = a:last + if empty(first) + let first = 1 + endif + if empty(last) + let last = line('$') + endif + return csv#EvalColumn(a:col, 'MaxColumn', first, last, 0) +endfu +fu! CSVCount(col, fmt, first, last, ...) "{{{3 + let first = a:first + let last = a:last + let distinct = 0 + if empty(first) + let first = 1 + endif + if empty(last) + let last = line('$') + endif + if !exists('s:additional') + let s:additional = {} + endif + if exists("a:1") && !empty(a:1) + let s:additional['distinct'] = 1 + endif + let result=csv#EvalColumn(a:col, 'CountColumn', first, last, distinct) + unlet! s:additional['distinct'] + return (empty(result) ? 0 : result) +endfu +fu! CSV_WCol(...) "{{{3 + " Needed for airline + try + if exists("a:1") && (a:1 == 'Name' || a:1 == 1) + return printf("%s", WColumn(1)) + else + return printf(" %d/%d", WColumn(), MaxColumns()) + endif + catch + return '' + endtry +endfun + " Initialize Plugin "{{{2 let b:csv_start = exists("b:csv_start") ? b:csv_start : 1 let b:csv_end = exists("b:csv_end") ? b:csv_end : line('$') diff --git a/vimfiles/plugin/csv.vim b/vimfiles/plugin/csv.vim index 78b05a1..9eb9dcf 100644 --- a/vimfiles/plugin/csv.vim +++ b/vimfiles/plugin/csv.vim @@ -25,6 +25,13 @@ endif com! -range -bang -nargs=? CSVTable call Table(0, , , ) fu! Table(bang, line1, line2, delim) + if match(split(&ft, '\.'), 'csv') > -1 + " Use CSVTabularize command + echohl WarningMsg + echomsg "For CSV files, use the :CSVTabularize command!" + echohl None + return + endif " 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] diff --git a/vimfiles/syntax/csv.vim b/vimfiles/syntax/csv.vim index 60fe3a0..dac3dab 100644 --- a/vimfiles/syntax/csv.vim +++ b/vimfiles/syntax/csv.vim @@ -21,7 +21,10 @@ fu! Warning(msg) "{{{3 echohl Normal endfu -fu! Esc(val, char) "{{2 +fu! Esc(val, char) "{{{3 + if empty(a:val) + return a:val + endif return '\V'.escape(a:val, '\\'.a:char).'\m' endfu @@ -43,8 +46,10 @@ fu! CheckSaneSearchPattern() "{{{3 " Check Comment setting if !exists("g:csv_comment") let b:csv_cmt = split(&cms, '%s') - else + elseif match(g:csv_comment, '%s') >= 0 let b:csv_cmt = split(g:csv_comment, '%s') + else + let b:csv_cmt = [g:csv_comment] endif @@ -57,15 +62,25 @@ fu! CheckSaneSearchPattern() "{{{3 " 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 = 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] - \ : '' + let s:cmts = b:csv_cmt[0] + let s:cmte = len(b:csv_cmt) == 2 ? b:csv_cmt[1] : '' + " Make the file start at the first actual CSV record (issue #71) + if !exists("b:csv_headerline") + let cmts = Esc(s:cmts, '') + let pattern = '\%^\(\%('.cmts.'.*\n\)\|\%(\s*\n\)\)\+' + let start = search(pattern, 'nWe', 10) + if start > 0 + let b:csv_headerline = start+1 + endif + endif + " escape '/' for syn match command + let s:cmts=Esc(s:cmts, '/') + let s:cmte=Esc(s:cmte, '/') if line('$') > 1 && (!exists("b:col") || empty(b:col)) " check for invalid pattern, ftplugin hasn't been loaded yet @@ -122,22 +137,33 @@ fu! DoHighlight() "{{{3 endfor endif " Comment regions - exe 'syn match CSVComment /'. Esc(s:cmts, '/'). '.*'. - \ (!empty(s:cmte) ? '\%('. Esc(s:cmte, '/'). '\)\?' + exe 'syn match CSVComment /'. s:cmts. '.*'. + \ (!empty(s:cmte) ? '\%('. s:cmte. '\)\?' \: ''). '/' hi def link CSVComment Comment endfun +fu! HiLink(name, target) "{{{3 + if !hlexists(a:name) + exe "hi def link" a:name a:target + endif +endfu + 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 + call HiLink("CSVColumnHeaderOdd", "WarningMsg") + call HiLink("CSVColumnHeaderEven", "WarningMsg") + if get(g:, 'csv_no_column_highlight', 0) + call HiLink("CSVColumnOdd", "Normal") + call HiLink("CSVColumnEven", "Normal") + else + call HiLink("CSVColumnOdd", "DiffAdd") + call HiLink("CSVColumnEven","DiffChange") + endif endfun " Main: {{{2