Add `exclude_/include_function` support
This is first step towards fixing #1046.
This commit is contained in:
parent
bc557bd656
commit
7bf025ca2b
|
@ -508,6 +508,23 @@ ascii Theme without any unicode characters at all
|
|||
(segment is included in all modes). When there are both
|
||||
``exclude_modes`` overrides ``include_modes``.
|
||||
|
||||
.. _config-themes-seg-exclude_function:
|
||||
|
||||
``exclude_function``, ``include_function``
|
||||
Function name in a form ``{name}`` or ``{module}.{name}`` (in the first
|
||||
form ``{module}`` defaults to ``powerline.selectors.{ext}``). Determines
|
||||
under which condition specific segment will be included or excluded. By
|
||||
default segment is always included and never excluded.
|
||||
``exclude_function`` overrides ``include_function``.
|
||||
|
||||
.. note::
|
||||
Options :ref:`exclude_/include_modes
|
||||
<config-themes-seg-exclude_modes>` complement
|
||||
``exclude_/include_functions``: segment will be included if it is
|
||||
included by either ``include_mode`` or ``include_function`` and will
|
||||
be excluded if it is excluded by either ``exclude_mode`` or
|
||||
``exclude_function``.
|
||||
|
||||
.. _config-themes-seg-display:
|
||||
|
||||
``display``
|
||||
|
|
|
@ -254,9 +254,15 @@ Segment dictionary contains the following keys:
|
|||
``side``
|
||||
Segment side: ``right`` or ``left``.
|
||||
|
||||
``exclude_modes``, ``include_modes``
|
||||
:ref:`Mode display control lists <config-themes-seg-exclude_modes>`. May be
|
||||
empty, but may not be ``None``.
|
||||
``display_condition```
|
||||
Contains function that takes three position parameters:
|
||||
:py:class:`powerline.PowerlineLogger` instance, :ref:`segment_info
|
||||
<dev-segments-info>` dictionary and current mode and returns either ``True``
|
||||
or ``False`` to indicate whether particular segment should be processed.
|
||||
|
||||
This key is constructed based on :ref:`exclude_/include_modes keys
|
||||
<config-themes-seg-exclude_modes>` and :ref:`exclude_/include_function keys
|
||||
<config-themes-seg-exclude_function>`.
|
||||
|
||||
``width``, ``align``
|
||||
:ref:`Width and align options <config-themes-seg-align>`. May be ``None``.
|
||||
|
|
|
@ -9,6 +9,7 @@ import logging
|
|||
|
||||
from collections import defaultdict
|
||||
from copy import copy
|
||||
from functools import partial
|
||||
|
||||
from powerline.lint.markedjson import load
|
||||
from powerline import generate_config_finder, get_config_paths, load_config
|
||||
|
@ -478,18 +479,18 @@ def check_matcher_func(ext, match_name, data, context, echoerr):
|
|||
echoerr(context='Error while loading matcher functions',
|
||||
problem='failed to load module {0}'.format(match_module),
|
||||
problem_mark=match_name.mark)
|
||||
return True, True
|
||||
return True, False, True
|
||||
except AttributeError:
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='failed to load matcher function {0}'.format(match_function),
|
||||
problem_mark=match_name.mark)
|
||||
return True, True
|
||||
return True, False, True
|
||||
|
||||
if not callable(func):
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='loaded "function" {0} is not callable'.format(match_function),
|
||||
problem_mark=match_name.mark)
|
||||
return True, True
|
||||
return True, False, True
|
||||
|
||||
if hasattr(func, 'func_code') and hasattr(func.func_code, 'co_argcount'):
|
||||
if func.func_code.co_argcount != 1:
|
||||
|
@ -502,7 +503,7 @@ def check_matcher_func(ext, match_name, data, context, echoerr):
|
|||
problem_mark=match_name.mark
|
||||
)
|
||||
|
||||
return True, False
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_ext(ext, data, context, echoerr):
|
||||
|
@ -561,6 +562,9 @@ def check_top_theme(theme, data, context, echoerr):
|
|||
return True, False, False
|
||||
|
||||
|
||||
function_name_re = '^(\w+\.)*[a-zA-Z_]\w*$'
|
||||
|
||||
|
||||
divider_spec = Spec().type(unicode).len(
|
||||
'le', 3, (lambda value: 'Divider {0!r} is too large!'.format(value))).copy
|
||||
ext_theme_spec = Spec().type(unicode).func(lambda *args: check_config('themes', *args)).copy
|
||||
|
@ -608,7 +612,8 @@ main_spec = (Spec(
|
|||
local_themes=Spec(
|
||||
__tabline__=ext_theme_spec(),
|
||||
).unknown_spec(
|
||||
lambda *args: check_matcher_func('vim', *args), ext_theme_spec()
|
||||
Spec().re(function_name_re).func(partial(check_matcher_func, 'vim')),
|
||||
ext_theme_spec()
|
||||
),
|
||||
).optional(),
|
||||
ipython=ext_spec().update(
|
||||
|
@ -801,6 +806,7 @@ shell_colorscheme_spec = (Spec(
|
|||
|
||||
generic_keys = set((
|
||||
'exclude_modes', 'include_modes',
|
||||
'exclude_function', 'include_function',
|
||||
'width', 'align',
|
||||
'name',
|
||||
'draw_soft_divider', 'draw_hard_divider',
|
||||
|
@ -935,7 +941,7 @@ def check_full_segment_data(segment, data, context, echoerr):
|
|||
return check_key_compatibility(segment_copy, data, context, echoerr)
|
||||
|
||||
|
||||
def import_segment(name, data, context, echoerr, module):
|
||||
def import_function(function_type, name, data, context, echoerr, module):
|
||||
context_has_marks(context)
|
||||
havemarks(name, module)
|
||||
|
||||
|
@ -949,7 +955,7 @@ def import_segment(name, data, context, echoerr, module):
|
|||
problem_mark=module.mark)
|
||||
return None
|
||||
except AttributeError:
|
||||
echoerr(context='Error while loading segment function (key {key})'.format(key=context_key(context)),
|
||||
echoerr(context='Error while loading {0} function (key {key})'.format(function_type, key=context_key(context)),
|
||||
problem='failed to load function {0} from module {1}'.format(name, module),
|
||||
problem_mark=name.mark)
|
||||
return None
|
||||
|
@ -964,6 +970,10 @@ def import_segment(name, data, context, echoerr, module):
|
|||
return func
|
||||
|
||||
|
||||
def import_segment(*args, **kwargs):
|
||||
return import_function('segment', *args, **kwargs)
|
||||
|
||||
|
||||
def check_segment_function(function_name, data, context, echoerr):
|
||||
havemarks(function_name)
|
||||
ext = data['ext']
|
||||
|
@ -1334,6 +1344,17 @@ def get_all_possible_functions(data, context, echoerr):
|
|||
yield func
|
||||
|
||||
|
||||
def check_exinclude_function(name, data, context, echoerr):
|
||||
ext = data['ext']
|
||||
module, name = name.rpartition('.')[::2]
|
||||
if not module:
|
||||
module = MarkedUnicode('powerline.selectors.' + ext, None)
|
||||
func = import_function('selector', name, data, context, echoerr, module=module)
|
||||
if not func:
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
args_spec = Spec(
|
||||
pl=Spec().error('pl object must be set by powerline').optional(),
|
||||
segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(),
|
||||
|
@ -1341,12 +1362,15 @@ args_spec = Spec(
|
|||
highlight_group_spec = Spec().type(unicode).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(),
|
||||
name=Spec().re('^[a-zA-Z_]\w*$').optional(),
|
||||
function=Spec().re('^(\w+\.)*[a-zA-Z_]\w*$').func(check_segment_function).optional(),
|
||||
function=Spec().re(function_name_re).func(check_segment_function).optional(),
|
||||
exclude_modes=Spec().list(vim_mode_spec()).optional(),
|
||||
include_modes=Spec().list(vim_mode_spec()).optional(),
|
||||
exclude_function=exinclude_spec().optional(),
|
||||
include_function=exinclude_spec().optional(),
|
||||
draw_hard_divider=Spec().type(bool).optional(),
|
||||
draw_soft_divider=Spec().type(bool).optional(),
|
||||
draw_inner_divider=Spec().type(bool).optional(),
|
||||
|
|
|
@ -113,13 +113,7 @@ def process_segment_lister(pl, segment_info, parsed_segments, side, mode, colors
|
|||
subsegment['priority'] *= subsegment_update['priority_multiplier']
|
||||
|
||||
subsegment_mode = subsegment_update.get('mode')
|
||||
if subsegment_mode and (
|
||||
subsegment_mode in subsegment['exclude_modes']
|
||||
or (
|
||||
subsegment['include_modes']
|
||||
and subsegment_mode not in subsegment['include_modes']
|
||||
)
|
||||
):
|
||||
if subsegment_mode and not subsegment['display_condition'](pl, segment_info, subsegment_mode):
|
||||
continue
|
||||
|
||||
process_segment(
|
||||
|
@ -218,6 +212,9 @@ def process_segment(pl, side, segment_info, parsed_segments, segment, mode, colo
|
|||
parsed_segments.append(segment)
|
||||
|
||||
|
||||
always_true = lambda pl, segment_info, mode: True
|
||||
|
||||
|
||||
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,
|
||||
|
@ -229,6 +226,60 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
return get_segment_key(merge, segment, theme_configs, data['segment_data'], key, function_name, name, module, default)
|
||||
data['get_key'] = get_key
|
||||
|
||||
def get_selector(function_name):
|
||||
if '.' in function_name:
|
||||
module, function_name = function_name.rpartition('.')[::2]
|
||||
else:
|
||||
module = 'powerline.selectors.' + ext
|
||||
function = get_module_attr(module, function_name, prefix='segment_generator/selector_function')
|
||||
if not function:
|
||||
pl.error('Failed to get segment selector, ignoring it')
|
||||
return function
|
||||
|
||||
def get_segment_selector(segment, selector_type):
|
||||
try:
|
||||
function_name = segment[selector_type + '_function']
|
||||
except KeyError:
|
||||
function = None
|
||||
else:
|
||||
function = get_selector(function_name)
|
||||
try:
|
||||
modes = segment[selector_type + '_modes']
|
||||
except KeyError:
|
||||
modes = None
|
||||
|
||||
if modes:
|
||||
if function:
|
||||
return lambda pl, segment_info, mode: (
|
||||
mode in modes
|
||||
or function(pl=pl, segment_info=segment_info, mode=mode)
|
||||
)
|
||||
else:
|
||||
return lambda pl, segment_info, mode: mode in modes
|
||||
else:
|
||||
if function:
|
||||
return lambda pl, segment_info, mode: (
|
||||
function(pl=pl, segment_info=segment_info, mode=mode)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
def gen_display_condition(segment):
|
||||
include_function = get_segment_selector(segment, 'include')
|
||||
exclude_function = get_segment_selector(segment, 'exclude')
|
||||
if include_function:
|
||||
if exclude_function:
|
||||
return lambda *args: (
|
||||
include_function(*args)
|
||||
and not exclude_function(*args))
|
||||
else:
|
||||
return include_function
|
||||
else:
|
||||
if exclude_function:
|
||||
return lambda *args: not exclude_function(*args)
|
||||
else:
|
||||
return always_true
|
||||
|
||||
def get(segment, side):
|
||||
segment_type = segment.get('type', 'function')
|
||||
try:
|
||||
|
@ -265,6 +316,8 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
get_key(True, segment, module, function_name, name, 'args', {}).items()
|
||||
))
|
||||
|
||||
display_condition = gen_display_condition(segment)
|
||||
|
||||
if segment_type == 'segment_list':
|
||||
# Handle startup and shutdown of _contents_func?
|
||||
subsegments = [
|
||||
|
@ -292,8 +345,7 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
'draw_hard_divider': None,
|
||||
'draw_inner_divider': None,
|
||||
'side': side,
|
||||
'exclude_modes': segment.get('exclude_modes', []),
|
||||
'include_modes': segment.get('include_modes', []),
|
||||
'display_condition': display_condition,
|
||||
'width': None,
|
||||
'align': None,
|
||||
'expand': None,
|
||||
|
@ -342,8 +394,7 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
'draw_soft_divider': segment.get('draw_soft_divider', True),
|
||||
'draw_inner_divider': segment.get('draw_inner_divider', False),
|
||||
'side': side,
|
||||
'exclude_modes': segment.get('exclude_modes', []),
|
||||
'include_modes': segment.get('include_modes', []),
|
||||
'display_condition': display_condition,
|
||||
'width': segment.get('width'),
|
||||
'align': segment.get('align', 'l'),
|
||||
'expand': expand_func,
|
||||
|
|
|
@ -136,9 +136,7 @@ class Theme(object):
|
|||
parsed_segments = []
|
||||
for segment in self.segments[line][side]:
|
||||
# No segment-local modes at this point
|
||||
if mode not in segment['exclude_modes'] and (
|
||||
not segment['include_modes'] or mode in segment['include_modes']
|
||||
):
|
||||
if segment['display_condition'](self.pl, segment_info, mode):
|
||||
process_segment(
|
||||
self.pl,
|
||||
side,
|
||||
|
|
|
@ -389,7 +389,7 @@ class TestThemeHierarchy(TestRender):
|
|||
])
|
||||
|
||||
|
||||
class TestModes(TestRender):
|
||||
class TestDisplayCondition(TestRender):
|
||||
@add_args
|
||||
def test_include_modes(self, p, config):
|
||||
config['themes/test/default']['segments'] = {
|
||||
|
@ -432,6 +432,87 @@ class TestModes(TestRender):
|
|||
self.assertRenderEqual(p, '{56} s1{6-}>>{--}', mode='m2')
|
||||
self.assertRenderEqual(p, '{56} s2{6-}>>{--}', mode='m3')
|
||||
|
||||
@add_args
|
||||
def test_exinclude_function_nonexistent_module(self, p, config):
|
||||
config['themes/test/default']['segments'] = {
|
||||
'left': [
|
||||
highlighted_string('s1', 'g1', exclude_function='xxx_nonexistent_module.foo'),
|
||||
highlighted_string('s2', 'g1', exclude_function='xxx_nonexistent_module.foo', include_function='xxx_nonexistent_module.bar'),
|
||||
highlighted_string('s3', 'g1', include_function='xxx_nonexistent_module.bar'),
|
||||
]
|
||||
}
|
||||
self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}')
|
||||
|
||||
@add_args
|
||||
def test_exinclude_function(self, p, config):
|
||||
config['themes/test/default']['segments'] = {
|
||||
'left': [
|
||||
highlighted_string('s1', 'g1', exclude_function='mod.foo'),
|
||||
highlighted_string('s2', 'g1', exclude_function='mod.foo', include_function='mod.bar'),
|
||||
highlighted_string('s3', 'g1', include_function='mod.bar'),
|
||||
]
|
||||
}
|
||||
launched = set()
|
||||
fool = [None]
|
||||
barl = [None]
|
||||
|
||||
def foo(*args, **kwargs):
|
||||
launched.add('foo')
|
||||
self.assertEqual(set(kwargs.keys()), set(('pl', 'segment_info', 'mode')))
|
||||
self.assertEqual(args, ())
|
||||
return fool[0]
|
||||
|
||||
def bar(*args, **kwargs):
|
||||
launched.add('bar')
|
||||
self.assertEqual(set(kwargs.keys()), set(('pl', 'segment_info', 'mode')))
|
||||
self.assertEqual(args, ())
|
||||
return barl[0]
|
||||
|
||||
with replace_item(sys.modules, 'mod', Args(foo=foo, bar=bar)):
|
||||
fool[0] = True
|
||||
barl[0] = True
|
||||
self.assertRenderEqual(p, '{56} s3{6-}>>{--}')
|
||||
self.assertEqual(launched, set(('foo', 'bar')))
|
||||
|
||||
fool[0] = False
|
||||
barl[0] = True
|
||||
self.assertRenderEqual(p, '{56} s1{56}>{56}s2{56}>{56}s3{6-}>>{--}')
|
||||
self.assertEqual(launched, set(('foo', 'bar')))
|
||||
|
||||
fool[0] = False
|
||||
barl[0] = False
|
||||
self.assertRenderEqual(p, '{56} s1{6-}>>{--}')
|
||||
self.assertEqual(launched, set(('foo', 'bar')))
|
||||
|
||||
fool[0] = True
|
||||
barl[0] = False
|
||||
self.assertRenderEqual(p, '{--}')
|
||||
self.assertEqual(launched, set(('foo', 'bar')))
|
||||
|
||||
@add_args
|
||||
def test_exinclude_modes_override_functions(self, p, config):
|
||||
config['themes/test/default']['segments'] = {
|
||||
'left': [
|
||||
highlighted_string('s1', 'g1', exclude_function='mod.foo', exclude_modes=['m2']),
|
||||
highlighted_string('s2', 'g1', exclude_function='mod.foo', include_modes=['m2']),
|
||||
highlighted_string('s3', 'g1', include_function='mod.foo', exclude_modes=['m2']),
|
||||
highlighted_string('s4', 'g1', include_function='mod.foo', include_modes=['m2']),
|
||||
]
|
||||
}
|
||||
fool = [None]
|
||||
|
||||
def foo(*args, **kwargs):
|
||||
return fool[0]
|
||||
|
||||
with replace_item(sys.modules, 'mod', Args(foo=foo)):
|
||||
fool[0] = True
|
||||
self.assertRenderEqual(p, '{56} s4{6-}>>{--}', mode='m2')
|
||||
self.assertRenderEqual(p, '{56} s3{56}>{56}s4{6-}>>{--}', mode='m1')
|
||||
|
||||
fool[0] = False
|
||||
self.assertRenderEqual(p, '{56} s2{56}>{56}s4{6-}>>{--}', mode='m2')
|
||||
self.assertRenderEqual(p, '{56} s1{6-}>>{--}', mode='m1')
|
||||
|
||||
|
||||
class TestSegmentAttributes(TestRender):
|
||||
@add_args
|
||||
|
|
Loading…
Reference in New Issue