Merge pull request #1371 from ZyX-I/clickable-tabs

Add support for clicking tabs in Vim
This commit is contained in:
Nikolai Aleksandrovich Pavlov 2015-05-29 22:54:22 +03:00
commit 0ea0291fca
10 changed files with 239 additions and 73 deletions

View File

@ -153,10 +153,34 @@ All keys in segments returned by the function override those obtained from
Detailed description of used dictionary keys:
.. _dev-segments-contents:
``contents``
Text displayed by segment. Should be a ``unicode`` (Python2) or ``str``
(Python3) instance.
``literal_contents``
Text that needs to be output literally (i.e. without passing through
:py:meth:`powerline.renderer.strwidth` to determine length, through
:py:meth:`powerline.renderer.escape` to escape special characters and
through :py:meth:`powerline.renderer.hl` to highlight it). Should be a tuple
``(contents_length, contents)`` where ``contents_length`` is an integer and
``contents`` is a ``unicode`` (Python2) or ``str`` (Python3) instance.
If this key is present and its second value is true then other contents keys
(:ref:`contents <dev-segments-contents>`, :ref:`after
<config-themes-seg-after>`, :ref:`before <config-themes-seg-before>`) will
be ignored.
.. note::
If target is inclusion of the segment in powerline upstream all segment
functions that output *only* subsegments with ``literal_contents`` key
must contain the following string in documentation::
No highlight groups are used (literal segment).
String must be present on the separate line.
.. _dev-segments-draw_inner_divider:
``draw_hard_divider``, ``draw_soft_divider``, ``draw_inner_divider``

View File

@ -7,6 +7,9 @@
"function": "powerline.listers.vim.tablister",
"exclude_function": "single_tab",
"segments": [
{
"function": "tab"
},
{
"function": "tabnr",
"after": " ",
@ -29,6 +32,12 @@
}
]
},
{
"function": "tab",
"args": {
"end": true
}
},
{
"type": "segment_list",
"function": "powerline.listers.vim.bufferlister",

View File

@ -198,10 +198,8 @@ args_spec = Spec(
segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(),
).unknown_spec(Spec(), Spec()).optional().copy
segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy
sub_segments_spec = Spec()
exinclude_spec = Spec().re(function_name_re).func(check_exinclude_function).copy
segment_spec = Spec(
type=Spec().oneof(type_keys).optional(),
segment_spec_base = Spec(
name=Spec().re('^[a-zA-Z_]\w*$').optional(),
function=Spec().re(function_name_re).func(check_segment_function).optional(),
exclude_modes=Spec().list(vim_mode_spec()).optional(),
@ -230,10 +228,14 @@ segment_spec = Spec(
':divider$',
(lambda value: 'it is recommended that divider highlight group names end with ":divider"')
).optional(),
segments=sub_segments_spec,
).func(check_full_segment_data)
sub_segments_spec.optional().list(segment_spec)
del sub_segments_spec
).func(check_full_segment_data).copy
subsegment_spec = segment_spec_base().update(
type=Spec().oneof(set((key for key in type_keys if key != 'segment_list'))).optional(),
)
segment_spec = segment_spec_base().update(
type=Spec().oneof(type_keys).optional(),
segments=Spec().optional().list(subsegment_spec),
)
segments_spec = Spec().optional().list(segment_spec).copy
segdict_spec = Spec(
left=segments_spec().context_message('Error while loading segments from left side (key {key})'),

View File

@ -381,7 +381,10 @@ def check_segment_function(function_name, data, context, echoerr):
hl_groups = []
divider_hl_group = None
hadproblem = False
if func.__doc__:
NO_H_G_USED_STR = 'No highlight groups are used (literal segment).'
H_G_USED_STR = 'Highlight groups used: '
LHGUS = len(H_G_USED_STR)
D_H_G_USED_STR = 'Divider highlight group used: '
@ -391,6 +394,20 @@ def check_segment_function(function_name, data, context, echoerr):
for i, line in enumerate(func.__doc__.split('\n')):
if H_G_USED_STR in line:
idx = line.index(H_G_USED_STR) + LHGUS
if hl_groups is None:
idx -= LHGUS
mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
echoerr(
context='Error while checking theme (key {key})'.format(key=context.key),
context_mark=function_name.mark,
problem=(
'found highlight group definition in addition to sentense stating that '
'no highlight groups are used'
),
problem_mark=mark,
)
hadproblem = True
continue
hl_groups.append((
line[idx:],
(mark_name, i + 1, idx + 1, func.__doc__),
@ -400,10 +417,24 @@ def check_segment_function(function_name, data, context, echoerr):
idx = line.index(D_H_G_USED_STR) + LDHGUS + 2
mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
divider_hl_group = MarkedUnicode(line[idx:-3], mark)
elif NO_H_G_USED_STR in line:
idx = line.index(NO_H_G_USED_STR)
if hl_groups:
mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
echoerr(
context='Error while checking theme (key {key})'.format(key=context.key),
context_mark=function_name.mark,
problem=(
'found sentense stating that no highlight groups are used '
'in addition to highlight group definition'
),
problem_mark=mark,
)
hadproblem = True
continue
hl_groups = None
pointer += len(line) + len('\n')
hadproblem = False
if divider_hl_group:
r = hl_exists(divider_hl_group, data, context, echoerr, allow_gradients=True)
if r:
@ -466,7 +497,7 @@ def check_segment_function(function_name, data, context, echoerr):
h[0], list_sep.join(r))
)
hadproblem = True
else:
elif hl_groups is not None:
r = hl_exists(function_name, data, context, echoerr, allow_gradients=True)
if r:
echoerr(

View File

@ -391,7 +391,10 @@ class Renderer(object):
segment['contents'] = translate_np(segment['contents'])
if calculate_contents_len:
for segment in segments:
segment['_contents_len'] = self.strwidth(segment['contents'])
if segment['literal_contents'][1]:
segment['_contents_len'] = segment['literal_contents'][0]
else:
segment['_contents_len'] = self.strwidth(segment['contents'])
def _render_length(self, theme, segments, divider_widths):
'''Update segments lengths and return them
@ -399,24 +402,52 @@ class Renderer(object):
segments_len = len(segments)
ret = 0
divider_spaces = theme.get_spaces()
prev_segment = theme.EMPTY_SEGMENT
try:
first_segment = next(iter((
segment
for segment in segments
if not segment['literal_contents'][1]
)))
except StopIteration:
first_segment = None
try:
last_segment = next(iter((
segment
for segment in reversed(segments)
if not segment['literal_contents'][1]
)))
except StopIteration:
last_segment = None
for index, segment in enumerate(segments):
side = segment['side']
segment_len = segment['_contents_len']
if not segment['literal_contents'][1]:
if side == 'left':
if segment is not last_segment:
compare_segment = next(iter((
segment
for segment in segments[index + 1:]
if not segment['literal_contents'][1]
)))
else:
compare_segment = theme.EMPTY_SEGMENT
else:
compare_segment = prev_segment
prev_segment = segments[index - 1] if index > 0 else theme.EMPTY_SEGMENT
next_segment = segments[index + 1] if index < segments_len - 1 else theme.EMPTY_SEGMENT
compare_segment = next_segment if side == 'left' else prev_segment
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
outer_padding = int(bool(
(index == 0 and side == 'left') or
(index == segments_len - 1 and side == 'right')
))
outer_padding = int(bool(
segment is first_segment
if side == 'left' else
segment is last_segment
))
draw_divider = segment['draw_' + divider_type + '_divider']
segment_len += outer_padding
if draw_divider:
segment_len += divider_widths[side][divider_type] + divider_spaces
draw_divider = segment['draw_' + divider_type + '_divider']
segment_len += outer_padding
if draw_divider:
segment_len += divider_widths[side][divider_type] + divider_spaces
prev_segment = segment
segment['_len'] = segment_len
ret += segment_len
@ -435,61 +466,92 @@ class Renderer(object):
'''
segments_len = len(segments)
divider_spaces = theme.get_spaces()
prev_segment = theme.EMPTY_SEGMENT
try:
first_segment = next(iter((
segment
for segment in segments
if not segment['literal_contents'][1]
)))
except StopIteration:
first_segment = None
try:
last_segment = next(iter((
segment
for segment in reversed(segments)
if not segment['literal_contents'][1]
)))
except StopIteration:
last_segment = None
for index, segment in enumerate(segments):
side = segment['side']
prev_segment = segments[index - 1] if index > 0 else theme.EMPTY_SEGMENT
next_segment = segments[index + 1] if index < segments_len - 1 else theme.EMPTY_SEGMENT
compare_segment = next_segment if side == 'left' else prev_segment
outer_padding = int(bool(
(index == 0 and side == 'left') or
(index == segments_len - 1 and side == 'right')
)) * ' '
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
divider_highlighted = ''
contents_raw = segment['contents']
contents_highlighted = ''
draw_divider = segment['draw_' + divider_type + '_divider']
# XXX Make sure self.hl() calls are called in the same order
# segments are displayed. This is needed for Vim renderer to work.
if draw_divider:
divider_raw = self.escape(theme.get_divider(side, divider_type))
if not segment['literal_contents'][1]:
if side == 'left':
contents_raw = outer_padding + contents_raw + (divider_spaces * ' ')
if segment is not last_segment:
compare_segment = next(iter((
segment
for segment in segments[index + 1:]
if not segment['literal_contents'][1]
)))
else:
compare_segment = theme.EMPTY_SEGMENT
else:
contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding
compare_segment = prev_segment
outer_padding = int(bool(
segment is first_segment
if side == 'left' else
segment is last_segment
)) * ' '
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
if divider_type == 'soft':
divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight'
divider_fg = segment[divider_highlight_group_key]['fg']
divider_bg = segment[divider_highlight_group_key]['bg']
else:
divider_fg = segment['highlight']['bg']
divider_bg = compare_segment['highlight']['bg']
divider_highlighted = ''
contents_raw = segment['contents']
contents_highlighted = ''
draw_divider = segment['draw_' + divider_type + '_divider']
if side == 'left':
if render_highlighted:
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
segment['_rendered_raw'] = contents_raw + divider_raw
segment['_rendered_hl'] = contents_highlighted + divider_highlighted
# XXX Make sure self.hl() calls are called in the same order
# segments are displayed. This is needed for Vim renderer to work.
if draw_divider:
divider_raw = self.escape(theme.get_divider(side, divider_type))
if side == 'left':
contents_raw = outer_padding + contents_raw + (divider_spaces * ' ')
else:
contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding
if divider_type == 'soft':
divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight'
divider_fg = segment[divider_highlight_group_key]['fg']
divider_bg = segment[divider_highlight_group_key]['bg']
else:
divider_fg = segment['highlight']['bg']
divider_bg = compare_segment['highlight']['bg']
if side == 'left':
if render_highlighted:
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
segment['_rendered_raw'] = contents_raw + divider_raw
segment['_rendered_hl'] = contents_highlighted + divider_highlighted
else:
if render_highlighted:
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
segment['_rendered_raw'] = divider_raw + contents_raw
segment['_rendered_hl'] = divider_highlighted + contents_highlighted
else:
if render_highlighted:
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
segment['_rendered_raw'] = divider_raw + contents_raw
segment['_rendered_hl'] = divider_highlighted + contents_highlighted
if side == 'left':
contents_raw = outer_padding + contents_raw
else:
contents_raw = contents_raw + outer_padding
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
segment['_rendered_raw'] = contents_raw
segment['_rendered_hl'] = contents_highlighted
prev_segment = segment
else:
if side == 'left':
contents_raw = outer_padding + contents_raw
else:
contents_raw = contents_raw + outer_padding
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
segment['_rendered_raw'] = contents_raw
segment['_rendered_hl'] = contents_highlighted
segment['_rendered_raw'] = ' ' * segment['literal_contents'][0]
segment['_rendered_hl'] = segment['literal_contents'][1]
yield segment
def escape(self, string):

View File

@ -127,6 +127,8 @@ def process_segment_lister(pl, segment_info, parsed_segments, side, mode, colors
colorscheme,
)
new_pslen = len(parsed_segments)
while parsed_segments[new_pslen - 1]['literal_contents'][1]:
new_pslen -= 1
if new_pslen > old_pslen + 1 and draw_inner_divider is not None:
for i in range(old_pslen, new_pslen - 1) if side == 'left' else range(old_pslen + 1, new_pslen):
parsed_segments[i]['draw_soft_divider'] = draw_inner_divider
@ -134,6 +136,8 @@ def process_segment_lister(pl, segment_info, parsed_segments, side, mode, colors
def set_segment_highlighting(pl, colorscheme, segment, mode):
if segment['literal_contents'][1]:
return True
try:
highlight_group_prefix = segment['highlight_group_prefix']
except KeyError:
@ -228,6 +232,7 @@ get_fallback_segment = {
'before': None,
'after': None,
'contents': '',
'literal_contents': (0, ''),
'priority': None,
'draw_soft_divider': True,
'draw_hard_divider': True,
@ -245,6 +250,7 @@ get_fallback_segment = {
'_contents_len': None,
}.copy
def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, get_module_attr, top_theme):
data = {
'default_module': default_module or 'powerline.segments.' + ext,
@ -373,6 +379,7 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
)
),
'contents': None,
'literal_contents': None,
'priority': None,
'draw_soft_divider': None,
'draw_hard_divider': None,
@ -421,6 +428,7 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
'after': get_key(False, segment, module, function_name, name, 'after', ''),
'contents_func': contents_func,
'contents': contents,
'literal_contents': (0, ''),
'priority': segment.get('priority', None),
'draw_hard_divider': segment.get('draw_hard_divider', True),
'draw_soft_divider': segment.get('draw_soft_divider', True),

View File

@ -740,3 +740,21 @@ def csv_col_current(pl, segment_info, display_name='auto', name_format=' ({colum
'contents': name_format.format(column_name=column_name),
'highlight_groups': ['csv:column_name', 'csv'],
}] if column_name else [])
@requires_segment_info
def tab(pl, segment_info, end=False):
'''Mark start of the clickable region for tabpage
:param bool end:
In place of starting region for the current tab end it.
No highlight groups are used (literal segment).
'''
try:
return [{
'contents': None,
'literal_contents': (0, '%{tabnr}T'.format(tabnr=('' if end else segment_info['tabnr']))),
}]
except KeyError:
return None

View File

@ -1,5 +1,5 @@
#!/usr/bin/vim -S
if has('multibyte')
if has('multi_byte')
if empty(&encoding)
call writefile(['&encoding option value is empty, even though Vim has +multibyte'], 'message.fail')
cquit

View File

@ -1234,6 +1234,18 @@ class TestVim(TestCase):
self.assertEqual(self.vim.tabnr(pl=pl, segment_info=segment_info, show_current=True), '1')
self.assertEqual(self.vim.tabnr(pl=pl, segment_info=segment_info, show_current=False), None)
def test_tab(self):
pl = Pl()
segment_info = vim_module._get_segment_info()
self.assertEqual(self.vim.tab(pl=pl, segment_info=segment_info), [{
'contents': None,
'literal_contents': (0, '%1T'),
}])
self.assertEqual(self.vim.tab(pl=pl, segment_info=segment_info, end=True), [{
'contents': None,
'literal_contents': (0, '%T'),
}])
def test_bufnr(self):
pl = Pl()
segment_info = vim_module._get_segment_info()

View File

@ -16,7 +16,7 @@ catch
cquit
endtry
if result isnot# '%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs '
if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  %2T2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs '
call writefile(['Unexpected tabline', result], 'message.fail')
cquit
endif
@ -30,7 +30,7 @@ catch
cquit
endtry
if result isnot# '%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
call writefile(['Unexpected tabline (2)', result], 'message.fail')
cquit
endif
@ -42,7 +42,7 @@ catch
call writefile(['Exception while evaluating &tabline (3)', v:exception], 'message.fail')
endtry
if result isnot# '%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
call writefile(['Unexpected tabline (3)', result], 'message.fail')
cquit
endif