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