forked from chrisbra/csv.vim
-
Notifications
You must be signed in to change notification settings - Fork 0
/
csv.vmb
5076 lines (4511 loc) · 179 KB
/
csv.vmb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
" Vimball Archiver by Charles E. Campbell
UseVimball
finish
ftplugin/csv.vim [[[1
3140
" Filetype plugin for editing CSV files. "{{{1
" Author: Christian Brabandt <cb@256bit.org>
" Version: 0.31
" Script: http://www.vim.org/scripts/script.php?script_id=2830
" License: VIM License
" Last Change: Thu, 15 Jan 2015 21:05:10 +0100
" Documentation: see :help ft-csv.txt
" GetLatestVimScripts: 2830 30 :AutoInstall: csv.vim
"
" Some ideas are taken from the wiki http://vim.wikia.com/wiki/VimTip667
" though, implementation differs.
" Plugin folklore "{{{1
if v:version < 700 || exists('b:did_ftplugin')
finish
endif
let b:did_ftplugin = 1
let s:cpo_save = &cpo
set cpo&vim
fu! <sid>DetermineSID()
let s:SID = matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_DetermineSID$')
endfu
call s:DetermineSID()
delf s:DetermineSID
let s:csv_numeric_sort = v:version > 704 || v:version == 704 && has("patch341")
if !s:csv_numeric_sort "{{{2
fu! <sid>CSVSortValues(i1, i2) "{{{3
return (a:i1+0) == (a:i2+0) ? 0 : (a:i1+0) > (a:i2+0) ? 1 : -1
endfu
endif
" Function definitions: "{{{1
fu! CSVArrangeCol(first, last, bang, limit) range "{{{2
if &ft =~? 'csv'
call <sid>ArrangeCol(a:first, a:last, a:bang, a:limit)
else
finish
endif
endfu
" Script specific functions "{{{2
fu! <sid>Warn(mess) "{{{3
echohl WarningMsg
echomsg "CSV: " . a:mess
echohl Normal
endfu
fu! <sid>Init(startline, endline, ...) "{{{3
" if a:1 is set, keep the b:delimiter
let keep = exists("a:1") && a:1
" Hilight Group for Columns
if exists("g:csv_hiGroup")
let s:hiGroup = g:csv_hiGroup
else
let s:hiGroup="WildMenu"
endif
if !exists("g:csv_hiHeader")
let s:hiHeader = "Title"
else
let s:hiHeader = g:csv_hiHeader
endif
exe "hi link CSVHeaderLine" s:hiHeader
" Determine default Delimiter
if !keep
if !exists("g:csv_delim")
let b:delimiter=<SID>GetDelimiter(a:startline, a:endline)
else
let b:delimiter=g:csv_delim
endif
endif
" Define custom commentstring
if !exists("g:csv_comment")
let b:csv_cmt = split(&cms, '%s')
else
let b:csv_cmt = split(g:csv_comment, '%s')
endif
if empty(b:delimiter) && !exists("b:csv_fixed_width")
call <SID>Warn("No delimiter found. See :h csv-delimiter to set it manually!")
" Use a sane default as delimiter:
let b:delimiter = ','
endif
let s:del='\%(' . b:delimiter . '\|$\)'
let s:del_noend='\%(' . b:delimiter . '\)'
" Pattern for matching a single column
if !exists("g:csv_strict_columns") && !exists("g:csv_col")
\ && !exists("b:csv_fixed_width")
" - Allow double quotes as escaped quotes only insides double quotes
" - Allow linebreaks only, if g:csv_nl isn't set (this is
" only allowed in double quoted strings see RFC4180), though this
" does not work with :WhatColumn and might mess up syntax
" highlighting.
" - optionally allow whitespace in front of the fields (to make it
" work with :ArrangeCol (that is actually not RFC4180 valid))
" - Should work with most ugly solutions that are available
let b:col='\%(\%(\%(' . (b:delimiter !~ '\s' ? '\s*' : '') .
\ '"\%(' . (exists("g:csv_nl") ? '\_' : '' ) .
\ '[^"]\|""\)*"\s*\)' . s:del . '\)\|\%(' .
\ '[^' . b:delimiter . ']*' . s:del . '\)\)'
let b:col_end='\%(\%(\%(' . (b:delimiter !~ '\s' ? '\s*' : '') .
\ '"\%(' . (exists("g:csv_nl") ? '\_' : '' ) .
\ '[^"]\|""\)*"\)' . s:del_noend . '\)\|\%(' .
\ '[^' . b:delimiter . ']*' . s:del_noend . '\)\)'
elseif !exists("g:csv_col") && exists("g:csv_strict_columns")
" strict columns
let b:col='\%([^' . b:delimiter . ']*' . s:del . '\)'
let b:col_end='\%([^' . b:delimiter . ']*' . s:del_noend . '\)'
elseif exists("b:csv_fixed_width")
" Fixed width column
let b:col=''
" Check for sane default
if b:csv_fixed_width =~? '[^0-9,]'
call <sid>Warn("Please specify the list of character columns" .
\ "like this: '1,3,5'. See also :h csv-fixedwidth")
return
endif
let b:csv_fixed_width_cols=split(b:csv_fixed_width, ',')
" Force evaluating as numbers
call map(b:csv_fixed_width_cols, 'v:val+0')
else
" User given column definition
let b:col = g:csv_col
let b:col_noend = g:csv_col
endif
" set filetype specific options
call <sid>LocalSettings('all')
" define buffer-local commands
call <SID>CommandDefinitions()
" Check Header line
" Defines which line is considered to be a header line
call <sid>CheckHeaderLine()
" CSV specific mappings
call <SID>CSVMappings()
" force reloading CSV Syntax Highlighting
if exists("b:current_syntax")
unlet b:current_syntax
" Force reloading syntax file
endif
call <sid>DoAutoCommands()
" enable CSV Menu
call <sid>Menu(1)
call <sid>DisableFolding()
if !exists("b:current_syntax")
silent do Syntax
endif
unlet! b:csv_start b:csv_end
" Remove configuration variables
let b:undo_ftplugin .= "| unlet! b:delimiter b:col"
\ . "| unlet! b:csv_fixed_width_cols b:csv_filter"
\ . "| unlet! b:csv_fixed_width b:csv_list b:col_width"
\ . "| unlet! b:csv_SplitWindow b:csv_headerline b:csv_cmt"
\ . "| unlet! b:csv_thousands_sep b:csv_decimal_sep"
\. " | unlet! b:browsefilter b:csv_cmt"
\. " | unlet! b:csv_arrange_leftalign"
" Delete all functions
" disabled currently, because otherwise when switching ft
" I think, all functions need to be read in again and this
" costs time.
endfu
fu! <sid>LocalSettings(type) "{{{3
if a:type == 'all'
" CSV local settings
setl nostartofline tw=0 nowrap
" undo when setting a new filetype
let b:undo_ftplugin = "setlocal sol& tw< wrap<"
" Set browsefilter
let b:browsefilter="CSV Files (*.csv, *.dat)\t*.csv;*.dat\n".
\ "All Files\t*.*\n"
if has("conceal")
setl cole=2 cocu=nc
let b:undo_ftplugin .= '| setl cole< cocu< '
endif
elseif a:type == 'fold'
let s:fdt = &l:fdt
let s:fcs = &l:fcs
if a:type == 'fold'
" Be sure to also fold away single screen lines
setl fen fdm=expr
setl fdl=0 fml=0 fdc=2
if !get(g:, 'csv_disable_fdt',0)
let &l:foldtext=strlen(v:folddashes) . ' lines hidden'
let &fcs=substitute(&fcs, 'fold:.,', '', '')
if !exists("b:csv_did_foldsettings")
let b:undo_ftplugin .= printf("|set fdt<|setl fcs=%s", escape(s:fcs, '\\| '))
endif
endif
if !exists("b:csv_did_foldsettings")
let b:undo_ftplugin .=
\ "| setl fen< fdm< fdl< fdc< fml< fde<"
let b:csv_did_foldsettings = 1
let b:undo_ftplugin .= "| unlet! b:csv_did_foldsettings"
endif
endif
endif
endfu
fu! <sid>DoAutoCommands() "{{{3
" Highlight column, on which the cursor is
if exists("g:csv_highlight_column") && g:csv_highlight_column =~? 'y'
exe "aug CSV_HI".bufnr('')
au!
exe "au CursorMoved <buffer=".bufnr('')."> HiColumn"
exe "au BufWinLeave <buffer=".bufnr('')."> sil! HiColumn!"
aug end
" Set highlighting for column, on which the cursor is currently
HiColumn
else
exe "aug CSV_HI".bufnr('')
exe "au! CursorMoved <buffer=".bufnr('').">"
aug end
exe "aug! CSV_HI".bufnr('')
" Remove any existing highlighting
HiColumn!
endif
" undo autocommand:
let b:undo_ftplugin .= '| exe "sil! au! CSV_HI CursorMoved <buffer> "'
let b:undo_ftplugin .= '| exe "sil! aug! CSV_HI" |exe "sil! HiColumn!"'
if has("gui_running") && !exists("#CSV_Menu#FileType")
augroup CSV_Menu
au!
au FileType * call <sid>Menu(&ft=='csv')
au BufEnter <buffer> call <sid>Menu(1) " enable
au BufLeave <buffer> call <sid>Menu(0) " disable
au BufNewFile,BufNew * call <sid>Menu(0)
augroup END
endif
endfu
fu! <sid>GetPat(colnr, maxcolnr, pat, allowmore) "{{{3
" if a:allowmmore, allows more to match after the pattern
if a:colnr > 1 && a:colnr < a:maxcolnr
if !exists("b:csv_fixed_width_cols")
return '^' . <SID>GetColPat(a:colnr-1,0) . '\%([^' .
\ b:delimiter . ']\{-}\)\?\zs' . a:pat . '\ze' .
\ (a:allowmore ? ('\%([^' . b:delimiter .']\{-}\)\?' .
\ b:delimiter . <SID>GetColPat(a:maxcolnr - a:colnr, 0). '$') : '')
else
return '\%' . b:csv_fixed_width_cols[(a:colnr - 1)] . 'c\zs'
\ . a:pat . '.\{-}\ze\%'
\ . (b:csv_fixed_width_cols[a:colnr]) . 'c\ze'
endif
elseif a:colnr == a:maxcolnr
if !exists("b:csv_fixed_width_cols")
" Allow space in front of the pattern, so that it works correctly
" even if :Arrange Col has been used #100
return '^' . <SID>GetColPat(a:colnr - 1,0) .
\ '\s*\zs' . a:pat . '\ze' . (a:allowmore ? '' : '$')
else
return '\%' . b:csv_fixed_width_cols[-1] .
\ 'c\zs' . a:pat . '\ze' . (a:allowmore ? '' : '$')
endif
else " colnr = 1
if !exists("b:csv_fixed_width_cols")
return '^' . '\%([^' . b:delimiter . ']\{-}\)\?\zs' . a:pat .
\ (a:allowmore ? ('\ze\%([^' . b:delimiter . ']*\)\?' . b:delimiter .
\ <SID>GetColPat(a:maxcolnr -1 , 0) . '$') : '')
else
return a:pat . '\ze.\{-}\%' . b:csv_fixed_width_cols[1] . 'c'
endif
endif
return ''
endfu
fu! <sid>SearchColumn(arg) "{{{3
try
let arglist=split(a:arg)
if len(arglist) == 1
let colnr=<SID>WColumn()
let pat=substitute(arglist[0], '^\(.\)\(.*\)\1$', '\2', '')
if pat == arglist[0]
throw "E684"
endif
else
" Determine whether the first word in the argument is a number
" (of the column to search).
let colnr = substitute( a:arg, '^\s*\(\d\+\)\s.*', '\1', '' )
" If it is _not_ a number,
if colnr == a:arg
" treat the whole argument as the pattern.
let pat = substitute(a:arg,
\ '^\s*\(\S\)\(.*\)\1\s*$', '\2', '' )
if pat == a:arg
throw "E684"
endif
let colnr = <SID>WColumn()
else
" if the first word tells us the number of the column,
" treat the rest of the argument as the pattern.
let pat = substitute(a:arg,
\ '^\s*\d\+\s*\(\S\)\(.*\)\1\s*$', '\2', '' )
if pat == a:arg
throw "E684"
endif
endif
endif
"catch /^Vim\%((\a\+)\)\=:E684/
catch /E684/ " catch error index out of bounds
call <SID>Warn("Error! Usage :SearchInColumn [<colnr>] /pattern/")
return 1
endtry
let maxcolnr = <SID>MaxColumns()
if colnr > maxcolnr
call <SID>Warn("There exists no column " . colnr)
return 1
endif
let @/ = <sid>GetPat(colnr, maxcolnr, '\%('.pat. '\)', 1)
try
" force redraw, so that the search pattern isn't shown
exe "norm! n\<c-l>"
catch /^Vim\%((\a\+)\)\=:E486/
" Pattern not found
echohl Error
echomsg "E486: Pattern not found in column " . colnr . ": " . pat
if &vbs > 0
echomsg substitute(v:exception, '^[^:]*:', '','')
endif
echohl Normal
endtry
endfu
fu! <sid>DeleteColumn(arg) "{{{3
let _wsv = winsaveview()
if a:arg =~ '^[/]'
let i = 0
let pat = a:arg[1:]
call cursor(1,1)
while search(pat, 'cW')
" Delete matching column
sil call <sid>DelColumn('')
let i+=1
endw
else
let i = 1
sil call <sid>DelColumn(a:arg)
endif
if i > 1
call <sid>Warn(printf("%d columns deleted", i))
elseif i == 1
call <sid>Warn("1 column deleted")
else
call <sid>Warn("no column deleted")
endif
call winrestview(_wsv)
endfu
fu! <sid>DelColumn(colnr) "{{{3
let maxcolnr = <SID>MaxColumns()
let _p = getpos('.')
if empty(a:colnr)
let colnr=<SID>WColumn()
else
let colnr=a:colnr
endif
if colnr > maxcolnr
call <SID>Warn("There exists no column " . colnr)
return
endif
if colnr != '1'
if !exists("b:csv_fixed_width_cols")
let pat= '^' . <SID>GetColPat(colnr-1,1) . b:col
else
let pat= <SID>GetColPat(colnr,0)
endif
else
" distinction between csv and fixed width does not matter here
let pat= '^' . <SID>GetColPat(colnr,0)
endif
if &ro
let ro = 1
setl noro
else
let ro = 0
endif
exe ':%s/' . escape(pat, '/') . '//'
call setpos('.', _p)
if ro
setl ro
endif
endfu
fu! <sid>HiCol(colnr, bang) "{{{3
if !a:bang
if a:colnr > <SID>MaxColumns()
call <SID>Warn("There exists no column " . a:colnr)
return
endif
if empty(a:colnr)
let colnr=<SID>WColumn()
else
let colnr=a:colnr
endif
if colnr==1
let pat='^'. <SID>GetColPat(colnr,0)
elseif !exists("b:csv_fixed_width_cols")
let pat='^'. <SID>GetColPat(colnr-1,1) . b:col
else
let pat=<SID>GetColPat(colnr,0)
endif
endif
if exists("*matchadd")
if exists("s:matchid")
" ignore errors, that come from already deleted matches
sil! call matchdelete(s:matchid)
endif
" Additionally, filter all matches, that could have been used earlier
let matchlist=getmatches()
call filter(matchlist, 'v:val["group"] !~ s:hiGroup')
call setmatches(matchlist)
if a:bang
return
endif
let s:matchid=matchadd(s:hiGroup, pat, 0)
elseif !a:bang
exe ":2match " . s:hiGroup . ' /' . pat . '/'
endif
endfu
fu! <sid>GetDelimiter(first, last) "{{{3
if !exists("b:csv_fixed_width_cols")
let _cur = getpos('.')
let _s = @/
let Delim= {0: ';', 1: ',', 2: '|', 3: ' ', 4: '\^'}
let temp = {}
" :silent :s does not work with lazyredraw
let _lz = &lz
set nolz
for i in values(Delim)
redir => temp[i]
exe "silent! ". a:first. ",". a:last. "s/" . i . "/&/nge"
redir END
endfor
let &lz = _lz
let Delim = map(temp, 'matchstr(substitute(v:val, "\n", "", ""), "^\\d\\+")')
let Delim = filter(temp, 'v:val=~''\d''')
let max = max(values(temp))
let result=[]
call setpos('.', _cur)
let @/ = _s
for [key, value] in items(Delim)
if value == max
return key
endif
endfor
return ''
else
" There is no delimiter for fixedwidth files
return ''
endif
endfu
fu! <sid>WColumn(...) "{{{3
" Return on which column the cursor is
let _cur = getpos('.')
if !exists("b:csv_fixed_width_cols")
if line('.') > 1 && mode('') != 'n'
" in insert mode, get line from above, just in case the current
" line is empty
let line = getline(line('.')-1)
else
let line = getline('.')
endif
" move cursor to end of field
"call search(b:col, 'ec', line('.'))
call search(b:col, 'ec')
let end=col('.')-1
let fields=(split(line[0:end],b:col.'\zs'))
let ret=len(fields)
if exists("a:1") && a:1 > 0
" bang attribute: Try to get the column name
let head = split(getline(get(b:, 'csv_headerline', 1)),b:col.'\zs')
" remove preceeding whitespace
if len(head) < ret
call <sid>Warn("Header has no field ". ret)
else
let ret = substitute(head[ret-1], '^\s\+', '', '')
" remove delimiter
let ret = substitute(ret, b:delimiter. '$', '', '')
endif
endif
else
let temp=getpos('.')[2]
let j=1
let ret = 1
for i in sort(b:csv_fixed_width_cols, s:csv_numeric_sort ? 'n' : 's:CSVSortValues')
if temp >= i
let ret = j
endif
let j += 1
endfor
endif
call setpos('.',_cur)
return ret
endfu
fu! <sid>MaxColumns(...) "{{{3
let this_col = exists("a:1")
"return maximum number of columns in first 10 lines
if !exists("b:csv_fixed_width_cols")
let i = this_col ? a:1 : get(b:, 'csv_headerline', 1)
while 1
let l = getline(i, (this_col ? i : i+10))
if empty(l) && i >= line('$')
break
endif
" Filter comments out
let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
call filter(l, 'v:val !~ pat')
if !empty(l) || this_col
break
else
let i+=10
endif
endw
if empty(l)
throw 'csv:no_col'
endif
let fields=[]
let result=0
for item in l
let temp=len(split(item, b:col.'\zs'))
let result=(temp>result ? temp : result)
endfor
return result
else
return len(b:csv_fixed_width_cols)
endif
endfu
fu! <sid>ColWidth(colnr, ...) "{{{3
" if a:1 is given, specifies the row, for which to calculate the width
"
" Return the width of a column
" Internal function
let width=20 "Fallback (wild guess)
let tlist=[]
if !exists("b:csv_fixed_width_cols")
if !exists("b:csv_list")
" only check first 10000 lines, to be faster
let last = line('$')
if exists("a:1") && !empty(a:1)
let last = a:1
endif
if !get(b:, 'csv_arrange_use_all_rows', 0)
if last > 10000
let last = 10000
call <sid>Warn('File too large, only checking the first 10000 rows for the width')
endif
endif
let b:csv_list=getline(1,last)
let pat = '^\s*\V'. escape(b:csv_cmt[0], '\\')
call filter(b:csv_list, 'v:val !~ pat')
call filter(b:csv_list, '!empty(v:val)')
call map(b:csv_list, 'split(v:val, b:col.''\zs'')')
endif
try
for item in b:csv_list
call add(tlist, get(item, a:colnr-1, ''))
endfor
" do not strip leading whitespace
call map(tlist, 'substitute(v:val, ".", "x", "g")')
call map(tlist, 'strlen(v:val)')
return max(tlist)
catch
throw "ColWidth-error"
return width
endtry
else
let cols = len(b:csv_fixed_width_cols)
if a:colnr == cols
return strlen(substitute(getline('$'), '.', 'x', 'g')) -
\ b:csv_fixed_width_cols[cols-1] + 1
elseif a:colnr < cols && a:colnr > 0
return b:csv_fixed_width_cols[a:colnr] -
\ b:csv_fixed_width_cols[(a:colnr - 1)]
else
throw "ColWidth-error"
return 0
endif
endif
endfu
fu! <sid>ArrangeCol(first, last, bang, limit, ...) range "{{{3
" a:1, optional width parameter of line from which to take the width
"
" explicitly give the range as argument to the function
if exists("b:csv_fixed_width_cols")
" Nothing to do
call <sid>Warn("ArrangeColumn does not work with fixed width column!")
return
endif
let cur=winsaveview()
" Force recalculation of Column width
let row = exists("a:1") ? a:1 : ''
if a:bang || !empty(row)
if a:bang && exists("b:col_width")
" Unarrange, so that if csv_arrange_align has changed
" it will be adjusted automatically
call <sid>PrepUnArrangeCol(a:first, a:last)
endif
" Force recalculating the Column width
unlet! b:csv_list b:col_width
elseif a:limit > -1 && a:limit < getfsize(fnamemodify(bufname(''), ':p'))
return
endif
let first = a:first
let last = a:last
if exists("b:csv_headerline")
if a:first < b:csv_headerline
let first = b:csv_headerline
endif
if a:last < b:csv_headerline
let last = b:csv_headerline
endif
endif
if first > line('$')
let first=line('$')
endif
if last > line('$')
let last=line('$')
endif
if &vbs
echomsg printf("ArrangeCol Start: %d, End: %d", first, last)
endif
if !exists("b:col_width")
call <sid>CalculateColumnWidth(row)
endif
" abort on empty file
if !len(b:col_width)
call <sid>Warn("No column data detected, aborting ArrangeCol command!")
return
endif
if &ro
" Just in case, to prevent the Warning
" Warning: W10: Changing read-only file
let ro = 1
setl noro
else
let ro = 0
endif
let s:count = 0
let _stl = &stl
let s:max = (last - first + 1) * len(b:col_width)
let s:temp = 0
try
exe "sil". first . ',' . last .'s/' . (b:col) .
\ '/\=<SID>Columnize(submatch(0))/' . (&gd ? '' : 'g')
finally
" Clean up variables, that were only needed for <sid>Columnize() function
unlet! s:columnize_count s:max_cols s:prev_line s:max s:count s:temp s:val
if ro
setl ro
unlet ro
endif
let &stl = _stl
call winrestview(cur)
endtry
endfu
fu! <sid>ProgressBar(cnt, max) "{{{3
if get(g:, 'csv_no_progress', 0) || a:max == 0
return
endif
let width = 40 " max width of progressbar
if width > &columns
let width = &columns
endif
let s:val = a:cnt * width / a:max
if (s:val > s:temp || a:cnt==1)
let &stl='%#DiffAdd#['.repeat('=', s:val).'>'. repeat(' ', width-s:val).']'.
\ (width < &columns ? ' '.100*s:val/width. '%%' : '')
redrawstatus
let s:temp = s:val
endif
endfu
fu! <sid>PrepUnArrangeCol(first, last) "{{{3
" Because of the way, Vim works with
" a:firstline and a:lastline parameter,
" explicitly give the range as argument to the function
if exists("b:csv_fixed_width_cols")
" Nothing to do
call <sid>Warn("UnArrangeColumn does not work with fixed width column!")
return
endif
let cur=winsaveview()
if &ro
" Just in case, to prevent the Warning
" Warning: W10: Changing read-only file
setl noro
endif
exe a:first . ',' . a:last .'s/' . (b:col) .
\ '/\=<SID>UnArrangeCol(submatch(0))/' . (&gd ? '' : 'g')
" Clean up variables, that were only needed for <sid>Columnize() function
call winrestview(cur)
endfu
fu! <sid>UnArrangeCol(match) "{{{3
" Strip leading white space, also trims empty records:
return substitute(a:match, '\%(^ \+\)\|\%( \+\ze'.b:delimiter. '\?$\)', '', 'g')
endfu
fu! <sid>CalculateColumnWidth(row) "{{{3
" Internal function, not called from external,
" does not work with fixed width columns
" row for the row for which to calculate the width
let b:col_width=[]
try
if exists("b:csv_headerline")
if line('.') < b:csv_headerline
call cursor(b:csv_headerline,1)
endif
endif
let s:max_cols=<SID>MaxColumns(line('.'))
for i in range(1,s:max_cols)
call add(b:col_width, <SID>ColWidth(i, a:row))
endfor
catch /csv:no_col/
call <sid>Warn("Error: getting Column numbers, aborting!")
catch /ColWidth/
call <sid>Warn("Error: getting Column Width, using default!")
endtry
" delete buffer content in variable b:csv_list,
" this was only necessary for calculating the max width
unlet! b:csv_list s:columnize_count s:decimal_column
endfu
fu! <sid>Columnize(field) "{{{3
" Internal function, not called from external,
" does not work with fixed width columns
if !exists("s:columnize_count")
let s:columnize_count = 0
endif
if !exists("s:max_cols")
let s:max_cols = len(b:col_width)
endif
if exists("s:prev_line") && s:prev_line != line('.')
let s:columnize_count = 0
endif
let s:count+=1
let s:prev_line = line('.')
" convert zero based indexed list to 1 based indexed list,
" Default: 20 width, in case that column width isn't defined
" Careful: Keep this fast! Using
" let width=get(b:col_width,<SID>WColumn()-1,20)
" is too slow, so we are using:
let colnr = s:columnize_count % s:max_cols
let width = get(b:col_width, colnr, 20)
let align = 'r'
if exists('b:csv_arrange_align')
let align=b:csv_arrange_align
let indx=match(align, '\*')
if indx > 0
let align = align[0:(indx-1)]. repeat(align[indx-1], len(b:col_width)-indx)
endif
let align_list=split(align, '\zs')
try
let align = align_list[colnr]
catch
let align = 'r'
endtry
endif
if ((align isnot? 'r' && align isnot? 'l' &&
\ align isnot? 'c' && align isnot? '.') || get(b:, 'csv_arrange_leftalign', 0))
let align = 'r'
endif
call <sid>ProgressBar(s:count,s:max)
let s:columnize_count += 1
let has_delimiter = (a:field[-1:] is? b:delimiter)
if align is? 'l'
" left-align content
return printf("%-*S%s", width-1,
\ (has_delimiter ? a:field[:-2] : a:field),
\ (has_delimiter ? b:delimiter : ' '))
elseif align is? 'c'
" center the column
let t = width - len(split(a:field, '\zs'))
let leftwidth = t/2
" uneven width, add one
let rightwidth = (t%2 ? leftwidth+1 : leftwidth)
let field = (has_delimiter ? a:field[:-2] : a:field). repeat(' ', rightwidth)
return printf("%*S%s", width , field, (has_delimiter ? b:delimiter : ' '))
elseif align is? '.'
if !exists("s:decimal_column")
let s:decimal_column = {}
endif
if get(s:decimal_column, colnr, 0) == 0
call <sid>CheckHeaderLine()
call <sid>NumberFormat()
let data = <sid>CopyCol('', colnr+1, '')[s:csv_fold_headerline : -1]
let pat1 = escape(s:nr_format[1], '.').'\zs[^'.s:nr_format[1].']*\ze'.
\ (has_delimiter ? b:delimiter : '').'$'
let pat2 = '\d\+\ze\%(\%('.escape(s:nr_format[1], '.'). '\d\+\)\|'.
\ (has_delimiter ? b:delimiter : '').'$\)'
let data1 = map(copy(data), 'matchstr(v:val, pat1)')
let data2 = map(data, 'matchstr(v:val, pat2)')
" strlen should be okay for decimals...
let data1 = map(data1, 'strlen(v:val)')
let data2 = map(data2, 'strlen(v:val)')
let dec = max(data1)
let scal = max(data2)
if dec + scal + 1 + (has_delimiter ? 1 : 0) > width
let width = dec + scal + 1 + (has_delimiter ? 1 :0)
let b:col_width[colnr] = width
endif
let s:decimal_column[colnr] = dec
else
let dec = get(s:decimal_column, colnr)
endif
let field = (has_delimiter ? a:field[:-2] : a:field)
let fmt = printf("%%%d.%df", width+1, dec)
try
if s:nr_format[1] isnot '.'
let field = substitute(field, s:nr_format[1], '.', 'g')
let field = substitute(field, s:nr_format[0], '', 'g')
endif
if field =~? '\h' " text in the column, can't be converted to float
throw "no decimal"
endif
let result = printf(fmt, str2float(field)). (has_delimiter ? b:delimiter : ' ')
catch
let result = printf("%*S", width+2, a:field)
endtry
return result
else
" right align
return printf("%*S", width+1 , a:field)
endif
endfun
fu! <sid>GetColPat(colnr, zs_flag) "{{{3
" Return Pattern for given column
if a:colnr > 1
if !exists("b:csv_fixed_width_cols")
let pat=b:col . '\{' . (a:colnr) . '\}'
else
if a:colnr >= len(b:csv_fixed_width_cols)
" Get last column
let pat='\%' . b:csv_fixed_width_cols[-1] . 'v.*'
else
let pat='\%' . b:csv_fixed_width_cols[(a:colnr - 1)] .
\ 'c.\{-}\%' . b:csv_fixed_width_cols[a:colnr] . 'v'
endif
endif
elseif !exists("b:csv_fixed_width_cols")
let pat=b:col
else
let pat='\%' . b:csv_fixed_width_cols[0] . 'v.\{-}' .
\ (len(b:csv_fixed_width_cols) > 1 ?
\ '\%' . b:csv_fixed_width_cols[1] . 'v' :
\ '')
endif
return pat . (a:zs_flag ? '\zs' : '')
endfu
fu! <sid>SetupAutoCmd(window,bufnr) "{{{3
" Setup QuitPre autocommand to quit cleanly
aug CSV_QuitPre
au!
exe "au QuitPre * call CSV_CloseBuffer(".winbufnr(a:window).")"
if !exists("##OptionSet")
exe "au CursorHold <buffer=".a:bufnr."> call CSV_SetSplitOptions(".a:window.")"
else
exe "au OptionSet foldcolumn,number,relativenumber call <sid>CSV_SetOption(".a:bufnr.
\ ", ".bufnr('%').", expand('<amatch>'), v:option_new)"
endif
exe "au VimResized,FocusLost,FocusGained <buffer=".a:bufnr."> call CSV_SetSplitOptions(".a:window.")"
aug END
endfu
fu! <sid>CSV_SetOption(csvfile, header, option, value) "{{{3
" only trigger if the option is called in the correct buffer
if getbufvar(a:csvfile, 'csv_SplitWindow') && bufnr('') == a:csvfile
call setbufvar(a:header, '&'.a:option, a:value)
endif
endfu
fu! <sid>SplitHeaderLine(lines, bang, hor) "{{{3
if exists("b:csv_fixed_width_cols")
call <sid>Warn("Header does not work with fixed width column!")
return
endif
" Check that there exists a header line
call <sid>CheckHeaderLine()
if !a:bang
" A Split Header Window already exists,
" first close the already existing Window
if exists("b:csv_SplitWindow")
call <sid>SplitHeaderLine(a:lines, 1, a:hor)
endif
" Split Window
let _stl = &l:stl
let _sbo = &sbo
let a = []
let b=b:col
let bufnr = bufnr('.')
if a:hor
setl scrollopt=hor scrollbind cursorbind
let _fdc = &l:fdc
let lines = empty(a:lines) ? s:csv_fold_headerline : a:lines
let a = getline(1,lines)
" Does it make sense to use the preview window?
" sil! pedit %
above sp +enew
call setline(1, a)
" Needed for syntax highlighting
"let b:col=b
"setl syntax=csv
sil! doautocmd FileType csv
noa 1
sil! sign unplace *
exe "resize" . lines
setl scrollopt=hor winfixheight nowrap cursorbind
let &l:stl="%#Normal#".repeat(' ',winwidth(0))
let s:local_stl = &l:stl
" set the foldcolumn to the same of the other window
let &l:fdc = _fdc
else
setl scrollopt=ver scrollbind cursorbind
noa 0
if a:lines[-1:] is? '!'
let a=<sid>CopyCol('',a:lines,'')
else
let a=<sid>CopyCol('',1, a:lines-1)
endif
" Does it make sense to use the preview window?
"vert sil! pedit |wincmd w | enew!
above vsp +enew
call append(0, a)
$d _
let b:col = b
sil! doautocmd FileType csv
" remove leading delimiter
exe "sil :%s/^". b:delimiter. "//e"
" remove trailing delimiter
exe "sil :%s/". b:delimiter. "\s*$//e"
syn clear
noa 0
let b:csv_SplitWindow = winnr()
sil :call <sid>ArrangeCol(1,line('$'), 1, -1)
sil! sign unplace *
exe "vert res" . len(split(getline(1), '\zs'))
call matchadd("CSVHeaderLine", b:col)
setl scrollopt=ver winfixwidth cursorbind nonu nornu fdc=0
endif
call <sid>SetupAutoCmd(winnr(),bufnr)
" disable airline
let w:airline_disabled = 1
let win = winnr()
setl scrollbind buftype=nowrite bufhidden=wipe noswapfile nobuflisted
noa wincmd p
let b:csv_SplitWindow = win
aug CSV_Preview
au!
au BufWinLeave <buffer> call <sid>SplitHeaderLine(0, 1, 0)
aug END
else
" Close split window
if !exists("b:csv_SplitWindow")
return
endif
try
let winnr = winnr()
if winnr == b:csv_SplitWindow || winbufnr(b:csv_SplitWindow) == bufnr('')
" window already closed
return
endif
exe b:csv_SplitWindow . "wincmd w"
if exists("_stl")
let &l:stl = _stl
endif
if exists("_sbo")
let &sbo = _sbo
endif
setl noscrollbind nocursorbind
call CSV_CloseBuffer(bufnr('%'))
catch /^Vim\%((\a\+)\)\=:E444/ " cannot close last window
catch /^Vim\%((\a\+)\)\=:E517/ " buffer already wiped
" no-op
finally