Add support for csv files

Equivalent to airline csv Christian Brabandt plugin integration, but uses Python
own facilities.

Note: filetype detection is out of the scope of the powerline responsibilities.
      CSV is supported by powerline, but only as long as filetype detection is
      done by Vim correctly. By default CSV files are neither detected nor
      highlighted.

Some known differences between powerline code and @chrisbra plugin with
corresponding airline integration:

1. Detection work differently. I cannot say which is better because I have not
   tested it much, but it is definitely different.
2. My variant is able to detect whether there is (no) header. (Of course,
   relying on Python code.) Airline is using a setting. (Both do not allow
   manual per-filetype setting.) Of course, user can force either variant (no
   headers at all or headers always).
3. My variant makes it possible to configure header output format, including
   truncating it at 15 characters (the default).
4. CSV plugin does not work with multiline CSV items (in some dialects one can
   use code like `abc;"I<CR>am<CR>multiline<CR>string"`). See `:h csv-column` on
   how to fix this if possible (will require also changing `<sid>WColumn`
   function because currently it only works with one line).
5. AFAIK Python does not such a thing as “fixed width CSV”. Thus I do not work
   with this, but CSV plugin does. Not sure whether it is different with
   space-separated CSV files though.
This commit is contained in:
ZyX 2014-11-09 23:11:15 +03:00
parent 28435f05d2
commit d392cf3322
5 changed files with 177 additions and 3 deletions

View File

@ -19,6 +19,9 @@
"winnr": "information:unimportant",
"tabnr": "file_directory",
"csv:column_number": "line_current",
"csv:column_name": "line_current_symbol",
"tab_nc:file_directory": "information:unimportant",
"tab_nc:file_name": "tab_nc:file_directory",
"tab_nc:tabnr": "tab_nc:file_directory",

View File

@ -95,6 +95,10 @@
"width": 4,
"align": "r"
},
{
"function": "csv_col_current",
"priority": 30
},
{
"type": "string",
"name": "line_current_symbol",

View File

@ -5,6 +5,9 @@ import os
import re
from collections import defaultdict
from csv import Sniffer, reader
from csv import Error as CSVError
try:
import vim
@ -22,6 +25,7 @@ from powerline.lib.humanize_bytes import humanize_bytes
from powerline.lib import wraps_saveargs as wraps
from powerline.segments.common.vcs import BranchSegment
from powerline.segments import with_docstring
from powerline.lib.unicode import string, unicode
try:
from __builtin__ import xrange as range
@ -627,3 +631,99 @@ def winnr(pl, segment_info, show_current=True):
winnr = segment_info['winnr']
if show_current or winnr != vim.current.window.number:
return str(winnr)
csv_cache = None
sniffer = Sniffer()
def detect_text_csv_dialect(text, display_name, header_text=None):
return (
sniffer.sniff(string(text)),
sniffer.has_header(string(header_text or text)) if display_name == 'auto' else display_name,
)
CSV_SNIFF_LINES = 100
CSV_PARSE_LINES = 10
def process_csv_buffer(pl, buffer, line, col, display_name):
global csv_cache
if csv_cache is None:
csv_cache = register_buffer_cache(defaultdict(lambda: (None, None, None)))
try:
cur_first_line = buffer[0]
except UnicodeDecodeError:
cur_first_line = vim.eval('strtrans(getline(1))')
dialect, has_header, first_line = csv_cache[buffer.number]
if dialect is None or (cur_first_line != first_line and display_name == 'auto'):
try:
text = '\n'.join(buffer[:CSV_SNIFF_LINES])
except UnicodeDecodeError: # May happen in Python 3
text = vim.eval('join(map(getline(1, {0}), "strtrans(v:val)"), "\\n")'.format(CSV_SNIFF_LINES))
try:
dialect, has_header = detect_text_csv_dialect(text, display_name)
except CSVError as e:
pl.warn('Failed to detect csv format: {0}', str(e))
# Try detecting using three lines only:
if line == 1:
rng = (0, line + 2)
elif line == len(buffer):
rng = (line - 3, line)
else:
rng = (line - 2, line + 1)
try:
dialect, has_header = detect_text_csv_dialect(
'\n'.join(buffer[rng[0]:rng[1]]),
display_name,
header_text='\n'.join(buffer[:4]),
)
except CSVError as e:
pl.error('Failed to detect csv format: {0}', str(e))
return None, None
if len(buffer) > 2:
csv_cache[buffer.number] = dialect, has_header, cur_first_line
column_number = len(list(reader(
buffer[max(0, line - CSV_PARSE_LINES):line - 1] + [buffer[line - 1][:col]], dialect=dialect))[-1]) or 1
if has_header:
try:
header = next(reader(buffer[0:1], dialect=dialect))
except UnicodeDecodeError:
header = next(reader([vim.eval('strtrans(getline(1))')], dialect=dialect))
column_name = header[column_number - 1]
else:
column_name = None
return unicode(column_number), column_name
@requires_segment_info
def csv_col_current(pl, segment_info, display_name='auto', name_format=' ({column_name:.15})'):
'''Display CSV column number and column name
Requires filetype to be set to ``csv``.
:param bool or str name:
May be ``True``, ``False`` and ``"auto"``. In the first case value from
the first raw will always be displayed. In the second case it will never
be displayed. In thi last case ``csv.Sniffer().has_header()`` will be
used to detect whether current file contains header in the first column.
:param str name_format:
String used to format column name (in case ``display_name`` is set to
``True`` or ``"auto"``). Accepts ``column_name`` keyword argument.
Highlight groups used: ``csv:column_number`` or ``csv``, ``csv:column_name`` or ``csv``.
'''
if vim_getbufoption(segment_info, 'filetype') != 'csv':
return None
line, col = segment_info['window'].cursor
column_number, column_name = process_csv_buffer(pl, segment_info['buffer'], line, col, display_name)
if not column_number:
return None
return [{
'contents': column_number,
'highlight_group': ['csv:column_number', 'csv'],
}] + ([{
'contents': name_format.format(column_name=column_name),
'highlight_group': ['csv:column_name', 'csv'],
}] if column_name else [])

View File

@ -22,19 +22,19 @@ def initialize():
ruby
if (not ($command_t.respond_to? 'active_finder'))
def $command_t.active_finder
@active_finder.class.name
@active_finder and @active_finder.class.name or ''
end
end
if (not ($command_t.respond_to? 'path'))
def $command_t.path
@path
@path or ''
end
end
def $powerline.commandt_set_active_finder
::VIM::command "let g:powerline_commandt_reply = '#{$command_t.active_finder}'"
end
def $powerline.commandt_set_path
::VIM::command "let g:powerline_commandt_reply = '#{$command_t.path.gsub(/'/, "''")}'"
::VIM::command "let g:powerline_commandt_reply = '#{($command_t.path or '').gsub(/'/, "''")}'"
end
'''
))

View File

@ -1196,6 +1196,73 @@ class TestVim(TestCase):
'highlight_group': ['tab_modified_indicator', 'modified_indicator'],
}])
def test_csv_col_current(self):
pl = Pl()
segment_info = vim_module._get_segment_info()
def csv_col_current(**kwargs):
self.vim.csv_cache and self.vim.csv_cache.clear()
return self.vim.csv_col_current(pl=pl, segment_info=segment_info, **kwargs)
buffer = segment_info['buffer']
try:
self.assertEqual(csv_col_current(), None)
buffer.options['filetype'] = 'csv'
self.assertEqual(csv_col_current(), None)
buffer[:] = ['1;2;3', '4;5;6']
vim_module._set_cursor(1, 1)
self.assertEqual(csv_col_current(), [{
'contents': '1', 'highlight_group': ['csv:column_number', 'csv']
}])
vim_module._set_cursor(2, 3)
self.assertEqual(csv_col_current(), [{
'contents': '2', 'highlight_group': ['csv:column_number', 'csv']
}])
vim_module._set_cursor(2, 3)
self.assertEqual(csv_col_current(display_name=True), [{
'contents': '2', 'highlight_group': ['csv:column_number', 'csv']
}, {
'contents': ' (2)', 'highlight_group': ['csv:column_name', 'csv']
}])
buffer[:0] = ['Foo;Bar;Baz']
vim_module._set_cursor(2, 3)
self.assertEqual(csv_col_current(), [{
'contents': '2', 'highlight_group': ['csv:column_number', 'csv']
}, {
'contents': ' (Bar)', 'highlight_group': ['csv:column_name', 'csv']
}])
buffer[len(buffer):] = ['1;"bc', 'def', 'ghi', 'jkl";3']
vim_module._set_cursor(5, 1)
self.assertEqual(csv_col_current(), [{
'contents': '2', 'highlight_group': ['csv:column_number', 'csv']
}, {
'contents': ' (Bar)', 'highlight_group': ['csv:column_name', 'csv']
}])
vim_module._set_cursor(7, 6)
self.assertEqual(csv_col_current(), [{
'contents': '3', 'highlight_group': ['csv:column_number', 'csv']
}, {
'contents': ' (Baz)', 'highlight_group': ['csv:column_name', 'csv']
}])
self.assertEqual(csv_col_current(name_format=' ({column_name:.1})'), [{
'contents': '3', 'highlight_group': ['csv:column_number', 'csv']
}, {
'contents': ' (B)', 'highlight_group': ['csv:column_name', 'csv']
}])
self.assertEqual(csv_col_current(display_name=True, name_format=' ({column_name:.1})'), [{
'contents': '3', 'highlight_group': ['csv:column_number', 'csv']
}, {
'contents': ' (B)', 'highlight_group': ['csv:column_name', 'csv']
}])
self.assertEqual(csv_col_current(display_name=False, name_format=' ({column_name:.1})'), [{
'contents': '3', 'highlight_group': ['csv:column_number', 'csv']
}])
self.assertEqual(csv_col_current(display_name=False), [{
'contents': '3', 'highlight_group': ['csv:column_number', 'csv']
}])
finally:
vim_module._bw(segment_info['bufnr'])
@classmethod
def setUpClass(cls):
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'path')))