rework gradient parsing; center bar; selectors

This commit is contained in:
Philip Wellnitz 2021-03-16 02:00:38 +09:00
parent 7310d53ade
commit 13d8261367
8 changed files with 468 additions and 53 deletions

View File

@ -901,6 +901,23 @@ class Powerline(object):
exc = e exc = e
yield FailedUnicode(safe_unicode(exc)) yield FailedUnicode(safe_unicode(exc))
def force_update(self, *args, **kwargs):
'''Force a segment to update itself.
'''
try:
self.update_renderer()
return self.renderer.force_update(*args, **kwargs)
except Exception as e:
exc = e
try:
self.exception('Failed to force segment update: {0}', str(e))
except Exception as e:
exc = e
ret = FailedUnicode(safe_unicode(exc))
if kwargs.get('output_width', False):
ret = ret, len(ret)
return ret
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
'''Setup the environment to use powerline. '''Setup the environment to use powerline.

View File

@ -4,13 +4,13 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
from copy import copy from copy import copy
from powerline.lib.unicode import unicode from powerline.lib.unicode import unicode
from colorsys import hsv_to_rgb, rgb_to_hsv
DEFAULT_MODE_KEY = None DEFAULT_MODE_KEY = None
ATTR_BOLD = 1 ATTR_BOLD = 1
ATTR_ITALIC = 2 ATTR_ITALIC = 2
ATTR_UNDERLINE = 4 ATTR_UNDERLINE = 4
ATTR_OVERLINE = 8
def get_attrs_flag(attrs): def get_attrs_flag(attrs):
'''Convert an attribute array to a renderer flag.''' '''Convert an attribute array to a renderer flag.'''
@ -21,47 +21,99 @@ def get_attrs_flag(attrs):
attrs_flag |= ATTR_ITALIC attrs_flag |= ATTR_ITALIC
if 'underline' in attrs: if 'underline' in attrs:
attrs_flag |= ATTR_UNDERLINE attrs_flag |= ATTR_UNDERLINE
if 'overline' in attrs:
attrs_flag |= ATTR_OVERLINE
return attrs_flag return attrs_flag
def lerp(a, b, x):
return (1-x) * a + x * b
def pick_gradient_value(grad_list, gradient_level): def round_col(*t):
return tuple(map(lambda x: int(x+0.5), t))
def pick_gradient_value(grad_list, gradient_level, is_hsv=False):
'''Given a list of colors and gradient percent, return a color that should be used. '''Given a list of colors and gradient percent, return a color that should be used.
Note: gradient level is not checked for being inside [0, 100] interval. Note: gradient level is not checked for being inside [0, 100] interval.
''' '''
return grad_list[int(round(gradient_level * (len(grad_list) - 1) / 100))] idx = len(grad_list) * (gradient_level / 100)
fr = idx % 1
idx = int(idx)
if idx - 1 < 0:
return grad_list[0] if not is_hsv else rgb_to_hex(*round_col(*hsv_to_rgb(*grad_list[0])))
elif idx >= len(grad_list):
return grad_list[-1] if not is_hsv else rgb_to_hex(*round_col(*hsv_to_rgb(*grad_list[-1])))
if is_hsv:
h0, s0, v0 = grad_list[idx-1]
h1, s1, v1 = grad_list[idx]
else:
h0, s0, v0 = rgb_to_hsv(*hex_to_rgb(grad_list[idx-1]))
h1, s1, v1 = rgb_to_hsv(*hex_to_rgb(grad_list[idx]))
# adapt hue for gradients to black/white
if s1 < 10 ** -3:
h1 = h0
elif s0 < 10 ** -3:
h0 = h1
c = round_col(*hsv_to_rgb(lerp(h0, h1, fr), lerp(s0, s1, fr), lerp(v0, v1, fr)))
return rgb_to_hex(*c)
def add_transparency(str):
return "0x{0:f>8}".format(str[2:])
def hex_to_cterm(s):
'''Converts a string describing a hex color (e.g. "0xff6600") to an xterm color index'''
return cterm_color(*hex_str_to_rgb(add_transparency(s)))
class Colorscheme(object): class Colorscheme(object):
def __init__(self, colorscheme_config, colors_config): def __init__(self, colorscheme_config, colors_config):
'''Initialize a colorscheme.''' '''Initialize a colorscheme.'''
self.colors = {} self.colors = {}
self.gradients = {} self.gradients = {}
self.gradient_types = {}
self.groups = colorscheme_config['groups'] self.groups = colorscheme_config['groups']
self.translations = colorscheme_config.get('mode_translations', {}) self.translations = colorscheme_config.get('mode_translations', {})
# Create a dict of color tuples with both a cterm and hex value # Create a dict of color tuples with both a cterm and hex value
for color_name, color in colors_config['colors'].items(): for color_name, color in colors_config['colors'].items():
try: if type(color) == int or type(color) == bool:
self.colors[color_name] = (color[0], int(color[1], 16))
except TypeError:
self.colors[color_name] = (color, cterm_to_hex[color]) self.colors[color_name] = (color, cterm_to_hex[color])
elif type(color) == str:
self.colors[color_name] = (hex_to_cterm(color), int(color, 16))
else:
self.colors[color_name] = (color[0], int(color[1], 16))
# Create a dict of gradient names with two lists: for cterm and hex # Create a dict of gradient names with two lists: for cterm and hex
# values. Two lists in place of one list of pairs were chosen because # values. Two lists in place of one list of pairs were chosen because
# true colors allow more precise gradients. # true colors allow more precise gradients.
for gradient_name, gradient in colors_config['gradients'].items(): for gradient_name, gradient in colors_config['gradients'].items():
if len(gradient) == 2: if type(gradient[0]) == list:
self.gradients[gradient_name] = ( if len(gradient) > 1 and type(gradient[1][0]) == float:
(gradient[0], [int(color, 16) for color in gradient[1]])) self.gradients[gradient_name]= gradient
self.gradient_types[gradient_name] = "hsv"
elif len(gradient) > 1: # legacy [[cterm], [hex]]
self.gradients[gradient_name] = [int(color, 16) for color in gradient[1]]
self.gradient_types[gradient_name] = "hex"
else: else:
self.gradients[gradient_name] = ( self.gradients[gradient_name] = [cterm_to_hex[color] for color in gradient[0]]
(gradient[0], [cterm_to_hex[color] for color in gradient[0]])) self.gradient_types[gradient_name] = "hex"
elif type(gradient[0]) == str:
self.gradients[gradient_name] = [int(color, 16) for color in gradient]
self.gradient_types[gradient_name] = "hex"
elif type(gradient[0]) == int or type(gradient[0]) == bool:
self.gradients[gradient_name] = [cterm_to_hex[color] for color in gradient[0]]
self.gradient_types[gradient_name] = "hex"
def get_gradient(self, gradient, gradient_level): def get_gradient(self, gradient, gradient_level):
if gradient in self.gradients: if gradient in self.gradients:
return tuple((pick_gradient_value(grad_list, gradient_level) for grad_list in self.gradients[gradient])) # cterm, hex
col = pick_gradient_value(self.gradients[gradient], gradient_level, is_hsv = (self. gradient_types[gradient] == "hsv"))
return (cterm_color(*hex_to_rgb(col)), col)
else: else:
return self.colors[gradient] return self.colors[gradient]
@ -105,14 +157,16 @@ class Colorscheme(object):
raise KeyError('Highlighting groups not found in colorscheme: ' + ', '.join(groups)) raise KeyError('Highlighting groups not found in colorscheme: ' + ', '.join(groups))
if gradient_level is None: if gradient_level is None:
pick_color = self.colors.__getitem__ pick_color = lambda str: (hex_to_cterm(str), int(add_transparency(str), 16)) if str. startswith('0x') or str.startswith('0X') else self.colors[str]
else: else:
pick_color = lambda gradient: self.get_gradient(gradient, gradient_level) pick_color = lambda str: (hex_to_cterm(str), int(add_transparency(str), 16)) if str. startswith('0x') or str.startswith('0X') else self.get_gradient(str, gradient_level)
# attrs and click are optional
return { return {
'fg': pick_color(group_props['fg']), 'fg': pick_color(group_props['fg']),
'bg': pick_color(group_props['bg']), 'bg': pick_color(group_props['bg']),
'attrs': get_attrs_flag(group_props.get('attrs', [])), 'attrs': get_attrs_flag(group_props.get('attrs', [])) if 'attrs' in group_props else 0,
'click': group_props['click'] if 'click' in group_props else None
} }
@ -145,3 +199,79 @@ cterm_to_hex = (
0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, # 24 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, # 24
0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee # 25 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee # 25
) )
def rgb_to_hex_str(r, g, b):
return "0x{r:02x}{g:02x}{b:02x}".format(r=r, g=g, b=b)
def rgb_to_hex(r, g, b):
return r << 16 | g << 8 | b
def hex_str_to_rgb(s):
return int(s[-6:-4], 16), int(s[-4:-2], 16), int(s[-2:], 16)
def hex_to_rgb(x):
return tuple((x >> i) & 0xff for i in range(16, -1, -8))
def cterm_grey_number(x):
if x < 14:
return 0
else:
n = (x - 8) // 10
m = (x - 8) % 10
if m < 5:
return n
else:
return n + 1
def cterm_grey_level(n):
if n == 0:
return 0
return 10 * n + 8
def cterm_grey_color(n):
return {0: 0, 25: 231}.get(n, 231 + n)
def cterm_rgb_number(x):
if x < 75:
return 0
n = (x - 55) // 40
m = (x - 55) % 40
if m < 20:
return n
else:
return n + 1
def cterm_rgb_level(n):
if n == 0:
return 0
return 40 * n + 55
def cterm_rgb_color(x, y, z):
return 16 + (x * 36) + (y * 6) + z
def cterm_color(r, g, b):
if 3 < r == g == b < 243:
return int(int(r - 7.5) / 10) + 232
gx = cterm_grey_number(r)
gy = cterm_grey_number(g)
gz = cterm_grey_number(b)
x = cterm_rgb_number(r)
y = cterm_rgb_number(g)
z = cterm_rgb_number(b)
if gx == gy == gz:
dgr = cterm_grey_level(gx) - r
dgg = cterm_grey_level(gy) - g
dgb = cterm_grey_level(gz) - b
dgrey = dgr ** 2 + dgg ** 2 + dgb ** 2
dr = cterm_rgb_level(gx) - r
dg = cterm_rgb_level(gy) - g
db = cterm_rgb_level(gz) - b
drgb = dr ** 2 + dg ** 2 + db ** 2
if dgrey < drgb:
return cterm_grey_color(gx)
else:
return cterm_rgb_color(x, y, z)
else:
return cterm_rgb_color(x, y, z)

View File

@ -267,7 +267,7 @@ class Renderer(object):
Maximum width text can occupy. May be exceeded if there are too much Maximum width text can occupy. May be exceeded if there are too much
non-removable segments. non-removable segments.
:param str side: :param str side:
One of ``left``, ``right``. Determines which side will be rendered. One of ``left``, ``right``, ``center``. Determines which side will be rendered.
If not present all sides are rendered. If not present all sides are rendered.
:param int line: :param int line:
Line number for which segments should be obtained. Is counted from Line number for which segments should be obtained. Is counted from
@ -315,6 +315,10 @@ class Renderer(object):
'hard': self.strwidth(theme.get_divider('right', 'hard')), 'hard': self.strwidth(theme.get_divider('right', 'hard')),
'soft': self.strwidth(theme.get_divider('right', 'soft')), 'soft': self.strwidth(theme.get_divider('right', 'soft')),
}, },
'center': {
'hard': self.strwidth(theme.get_divider('center', 'hard')),
'soft': self.strwidth(theme.get_divider('center', 'soft')),
},
} }
hl_join = staticmethod(''.join) hl_join = staticmethod(''.join)
@ -353,23 +357,28 @@ class Renderer(object):
divider_widths = self.compute_divider_widths(theme) divider_widths = self.compute_divider_widths(theme)
# Create an ordered list of segments that can be dropped # Create an ordered list of segments that can be dropped
segments_priority = sorted((segment for segment in segments if segment['priority'] is not None), key=lambda segment: segment['priority'], reverse=True) segments_priority = sorted((segment for segment in segments if segment['priority'] is not None), key=lambda segment: (-segment['priority'], segment['_len']))
no_priority_segments = filter(lambda segment: segment['priority'] is None, segments) no_priority_segments = filter(lambda segment: segment['priority'] is None, segments)
current_width = self._render_length(theme, segments, divider_widths) current_width = self._render_length(theme, segments, divider_widths)
if current_width > width: if current_width > width:
for segment in chain(segments_priority, no_priority_segments): for segment in chain(segments_priority, no_priority_segments):
if segment['truncate'] is not None: if segment['truncate'] is not None:
segment['contents'] = segment['truncate'](self.pl, current_width - width, segment) segment['contents'] = segment['truncate'](self.pl, current_width - width, segment)
self._prepare_segments(segments, output_width or width)
current_width = self._render_length(theme, segments, divider_widths)
if current_width <= width:
break
if current_width > width:
segments_priority = iter(segments_priority) segments_priority = iter(segments_priority)
if current_width > width and len(segments) > 100: if current_width > width and len(segments) > 100:
# When there are too many segments use faster, but less correct # When there are too many segments use faster, but less correct
# algorithm for width computation # algorithm for width computation
diff = current_width - width
for segment in segments_priority: for segment in segments_priority:
segments.remove(segment) segments.remove(segment)
diff -= segment['_len'] current_width -= segment['_len']
if diff <= 0: if current_width <= width:
break break
current_width = self._render_length(theme, segments, divider_widths) current_width = self._render_length(theme, segments, divider_widths)
if current_width > width: if current_width > width:
@ -409,6 +418,54 @@ class Renderer(object):
return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width) return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width)
def force_update(self, mode, segment_name, matcher_info, segment_info = None, *args, **kwargs):
'''Force the first segment with name ``segment_name`` to update its content.
'''
theme = self.get_theme(matcher_info)
self.do_force_update(
mode,
segment_name,
theme,
segment_info=self.get_segment_info(segment_info, mode),
*args,
**kwargs)
def do_force_update(self, mode, segment_name, theme, segment_info = None, *args, **kwargs):
'''Force the first segment with name ``segment_name`` to update its content.
May silently render segment to obtain their ``payload_name``.
Only works for ThreadedSegments
'''
for i in range(0, len(theme.segments)):
for side in theme.segments[i]:
target = [segment for segment in theme.segments[i][side]
if 'type' in segment and segment['type'] == 'function' and
'name' in segment and segment['name'] == segment_name]
if len(target) > 0:
try:
target[0]['contents_func'](theme.pl, segment_info, force_update=True)
except TypeError:
pass
return
# Some segments have differing name and payload_name, so compute the latter
for i in range(0, len(theme.segments)):
for side in theme.segments[i]:
for segment in theme.segments[i][side]:
if segment['type'] != 'function':
continue
contents = segment['contents_func'](theme.pl, segment_info)
if contents == None:
continue
pns = [True for seg in contents if 'payload_name' in seg
and seg['payload_name'] == segment_name]
if len(pns):
try:
segment['contents_func'](theme.pl, segment_info, force_update=True)
except TypeError:
pass
return
return
def _prepare_segments(self, segments, calculate_contents_len): def _prepare_segments(self, segments, calculate_contents_len):
'''Translate non-printable characters and calculate segment width '''Translate non-printable characters and calculate segment width
''' '''
@ -421,6 +478,13 @@ class Renderer(object):
else: else:
segment['_contents_len'] = self.strwidth(segment['contents']) segment['_contents_len'] = self.strwidth(segment['contents'])
def _compare_bg(self, first_bg, second_bg):
if not first_bg and not second_bg:
return True
if not first_bg or not second_bg:
return False
return first_bg[0] == second_bg[0] and first_bg[1] == second_bg[1]
def _render_length(self, theme, segments, divider_widths): def _render_length(self, theme, segments, divider_widths):
'''Update segments lengths and return them '''Update segments lengths and return them
''' '''
@ -448,7 +512,7 @@ class Renderer(object):
side = segment['side'] side = segment['side']
segment_len = segment['_contents_len'] segment_len = segment['_contents_len']
if not segment['literal_contents'][1]: if not segment['literal_contents'][1]:
if side == 'left': if side != 'right':
if segment is not last_segment: if segment is not last_segment:
compare_segment = next(iter(( compare_segment = next(iter((
segment segment
@ -460,12 +524,16 @@ class Renderer(object):
else: else:
compare_segment = prev_segment compare_segment = prev_segment
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' divider_type = 'soft' if self._compare_bg(compare_segment['highlight']['bg'], segment['highlight']['bg']) else 'hard'
outer_padding = int(bool( outer_padding = int(bool(
segment is first_segment segment is first_segment
if side == 'left' else if side == 'left' else
segment is last_segment segment is last_segment
if side == 'right' else
segment is first_segment or segment is last_segment
if side == 'center' else
False
)) * theme.outer_padding )) * theme.outer_padding
draw_divider = segment['draw_' + divider_type + '_divider'] draw_divider = segment['draw_' + divider_type + '_divider']
@ -492,6 +560,7 @@ class Renderer(object):
segments_len = len(segments) segments_len = len(segments)
divider_spaces = theme.get_spaces() divider_spaces = theme.get_spaces()
prev_segment = theme.EMPTY_SEGMENT prev_segment = theme.EMPTY_SEGMENT
next_segment = theme.EMPTY_SEGMENT
try: try:
first_segment = next(iter(( first_segment = next(iter((
segment segment
@ -512,7 +581,6 @@ class Renderer(object):
for index, segment in enumerate(segments): for index, segment in enumerate(segments):
side = segment['side'] side = segment['side']
if not segment['literal_contents'][1]: if not segment['literal_contents'][1]:
if side == 'left':
if segment is not last_segment: if segment is not last_segment:
compare_segment = next(iter(( compare_segment = next(iter((
segment segment
@ -520,15 +588,21 @@ class Renderer(object):
if not segment['literal_contents'][1] if not segment['literal_contents'][1]
))) )))
else: else:
compare_segment = theme.EMPTY_SEGMENT next_segment = theme.EMPTY_SEGMENT
if side != 'right':
compare_segment = next_segment
else: else:
compare_segment = prev_segment compare_segment = prev_segment
outer_padding = int(bool( outer_padding = int(bool(
segment is first_segment segment is first_segment
if side == 'left' else if side == 'left' else
segment is last_segment segment is last_segment
if side == 'right' else
segment is first_segment or segment is last_segment
if side == 'center' else
False
)) * theme.outer_padding * ' ' )) * theme.outer_padding * ' '
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' divider_type = 'soft' if self._compare_bg(compare_segment['highlight']['bg'], segment['highlight']['bg']) else 'hard'
divider_highlighted = '' divider_highlighted = ''
contents_raw = segment['contents'] contents_raw = segment['contents']
@ -541,9 +615,9 @@ class Renderer(object):
# XXX Make sure self.hl() calls are called in the same order # XXX Make sure self.hl() calls are called in the same order
# segments are displayed. This is needed for Vim renderer to work. # segments are displayed. This is needed for Vim renderer to work.
if draw_divider: if draw_divider and (side != 'center' or segment != last_segment):
divider_raw = self.escape(theme.get_divider(side, divider_type)) divider_raw = self.escape(theme.get_divider(side, divider_type))
if side == 'left': if side != 'right':
contents_raw = outer_padding + contents_raw + (divider_spaces * ' ') contents_raw = outer_padding + contents_raw + (divider_spaces * ' ')
else: else:
contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding
@ -556,25 +630,31 @@ class Renderer(object):
divider_fg = segment['highlight']['bg'] divider_fg = segment['highlight']['bg']
divider_bg = compare_segment['highlight']['bg'] divider_bg = compare_segment['highlight']['bg']
if side == 'left': if side != 'right':
if render_highlighted: if render_highlighted:
contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args) contents_highlighted = self.hl(self.escape(contents_raw),
next_segment=next_segment,
**segment_hl_args)
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args) divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
segment['_rendered_raw'] = contents_raw + divider_raw segment['_rendered_raw'] = contents_raw + divider_raw
segment['_rendered_hl'] = contents_highlighted + divider_highlighted segment['_rendered_hl'] = contents_highlighted + divider_highlighted
else: else:
if render_highlighted: if render_highlighted:
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args) divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args) contents_highlighted = self.hl(self.escape(contents_raw),
next_segment=next_segment,
**segment_hl_args)
segment['_rendered_raw'] = divider_raw + contents_raw segment['_rendered_raw'] = divider_raw + contents_raw
segment['_rendered_hl'] = divider_highlighted + contents_highlighted segment['_rendered_hl'] = divider_highlighted + contents_highlighted
else: else:
if side == 'left': if side == 'left' or (side == 'center' and segment != last_segment):
contents_raw = outer_padding + contents_raw contents_raw = outer_padding + contents_raw
else: else:
contents_raw = contents_raw + outer_padding contents_raw = contents_raw + outer_padding
contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args) contents_highlighted = self.hl(self.escape(contents_raw),
next_segment=next_segment,
**segment_hl_args)
segment['_rendered_raw'] = contents_raw segment['_rendered_raw'] = contents_raw
segment['_rendered_hl'] = contents_highlighted segment['_rendered_hl'] = contents_highlighted
prev_segment = segment prev_segment = segment

View File

@ -2,6 +2,7 @@
from __future__ import (unicode_literals, division, absolute_import, print_function) from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.lib.watcher import create_file_watcher from powerline.lib.watcher import create_file_watcher
from powerline.lib.dict import mergedicts_copy
def list_segment_key_values(segment, theme_configs, segment_data, key, function_name=None, name=None, module=None, default=None): def list_segment_key_values(segment, theme_configs, segment_data, key, function_name=None, name=None, module=None, default=None):
@ -262,14 +263,48 @@ 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) return get_segment_key(merge, segment, theme_configs, data['segment_data'], key, function_name, name, module, default)
data['get_key'] = get_key data['get_key'] = get_key
def get_selector(function_name): def get_selector(function_details):
function_name = None
simple_mode = True
if isinstance(function_details, str):
# simple definition of include/exclude function
function_name = function_details
if isinstance(function_details, dict):
function_details = dict((
(str(k), v)
for k, v in
function_details.items()
))
function_name = function_details.get('function')
simple_mode = False
if not function_name:
pl.error('Failed to get segment selector name, ignoring the selector')
return None
if '.' in function_name: if '.' in function_name:
module, function_name = function_name.rpartition('.')[::2] module, function_name = function_name.rpartition('.')[::2]
else: else:
module = 'powerline.selectors.' + ext module = 'powerline.selectors.' + ext
function = get_module_attr(module, function_name, prefix='segment_generator/selector_function') function = get_module_attr(module, function_name, prefix='segment_generator/selector_function')
if getattr(function, 'powerline_layered_selector', False):
if simple_mode or not 'args' in function_details:
# simple mode doesn't support args for include/exclude functions
function = None
else:
layered_args = dict((
(str(k), v) if not getattr(function, 'powerline_recursive_selector', False)
else (str(k), get_selector(v))
for k, v in
function_details.get('args').items()
))
try:
function = function(**layered_args)
except TypeError:
function = None
if not function: if not function:
pl.error('Failed to get segment selector, ignoring it') pl.error('Failed to get segment selector "{0}", ignoring it'.format(function_name))
return function return function
def get_segment_selector(segment, selector_type): def get_segment_selector(segment, selector_type):
@ -370,12 +405,13 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
'divider_highlight_group': None, 'divider_highlight_group': None,
'before': None, 'before': None,
'after': None, 'after': None,
'contents_func': lambda pl, segment_info, parsed_segments, side, mode, colorscheme: ( 'contents_func': lambda pl, segment_info, parsed_segments, side, mode, colorscheme, **opt: (
process_segment_lister( process_segment_lister(
pl, segment_info, parsed_segments, side, mode, colorscheme, pl, segment_info, parsed_segments, side, mode, colorscheme,
patcher_args=args, patcher_args=args,
subsegments=subsegments, subsegments=subsegments,
lister=_contents_func, lister=_contents_func,
**opt
) )
), ),
'contents': None, 'contents': None,
@ -409,9 +445,9 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
args[str('create_watcher')] = create_watcher args[str('create_watcher')] = create_watcher
if hasattr(_contents_func, 'powerline_requires_segment_info'): if hasattr(_contents_func, 'powerline_requires_segment_info'):
contents_func = lambda pl, segment_info: _contents_func(pl=pl, segment_info=segment_info, **args) contents_func = lambda pl, segment_info, **opt: _contents_func(pl=pl, segment_info=segment_info, **mergedicts_copy(args, opt))
else: else:
contents_func = lambda pl, segment_info: _contents_func(pl=pl, **args) contents_func = lambda pl, segment_info, **opt: _contents_func(pl=pl, **mergedicts_copy(args, opt))
else: else:
startup_func = None startup_func = None
shutdown_func = None shutdown_func = None

View File

@ -0,0 +1,43 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import layered_selector, recursive_selector
@layered_selector
def mode(target_modes):
'''Returns True if the current mode to is contained in ``target_mode``
:param list target_modes:
The modes to filter.
'''
return lambda pl, segment_info, mode: (
mode in target_mode
)
@layered_selector
@recursive_selector
def all_of(**kwargs):
'''Checks whether all of the given conditions are satisfied
:param args condition:
Any argument passed to this selector will be interpreted as a selector on its own that may have arguments.
'''
return lambda pl, segment_info, mode: (
all([func(pl=pl, segment_info=segment_info, mode=mode) for arg, func in kwargs.items() if func])
)
@layered_selector
@recursive_selector
def any_of(**kwargs):
'''Checks whether any of the given conditions are satisfied
:param kwargs condition:
Any argument passed to this selector will be interpreted as a selector on its own that may have arguments.
'''
return lambda pl, segment_info, mode: (
any([func(pl=pl, segment_info=segment_info, mode=mode) for arg, func in kwargs.items() if func])
)

View File

@ -0,0 +1,33 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import layered_selector
from datetime import datetime, timezone
@layered_selector
def time(target_start_time, target_end_time, time_format = '%H:%M', time_zone = None):
'''Returns True if the current time to is between ``target_start_time`` and ``target_end_time``.
Times are to be specified in strftime-style format ``time_format``.
:param string target_start_time:
The (inclusive) start time.
:param string target_end_time:
The (exclusive) end time.
:param string time_format:
The strftime-style format to use for the given times.
:param string time_zone:
The time zone to use for the current time.
'''
try:
tz = datetime.strptime(time_zone, '%z').tzinfo if time_zone else None
except ValueError:
tz = None
def selector(pl, segment_info, mode):
nw = datetime.now(tz)
cur_time = datetime.strptime(nw.strftime(time_format), time_format)
return datetime.strptime(target_start_time, time_format) <= cur_time \
and cur_time < datetime.strptime(target_end_time, time_format)
return selector

View File

@ -0,0 +1,67 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import layered_selector
@layered_selector
def output(target_outputs):
'''Returns True if the current output rendered to is contained in ``target_output``
:param list target_outputs:
The outputs to filter.
'''
return lambda pl, segment_info, mode: (
'output' in segment_info
and segment_info['output'] in target_outputs
)
@layered_selector
def channel_full(channel_name):
'''Returns True while the specified channel exists and is filled with any value.
:param string channel_name:
The channel to check.
'''
return lambda pl, segment_info, mode: (
'payloads' in segment_info
and channel_name in segment_info['payloads']
and segment_info['payloads'][channel_name]
)
@layered_selector
def channel_empty(channel_name):
'''Returns True while the specified channel is empty or does not exist
:param string channel_name:
The channel to check.
'''
return lambda pl, segment_info, mode: (
not 'payloads' in segment_info
or not channel_name in segment_info['payloads']
or not segment_info['payloads'][channel_name]
)
@layered_selector
def channel_has_value(channel_name, value):
'''Returns True while the specified channel is filled with the specified value
:param string channel_name:
The channel to check.
:param string value:
The value to check against.
'''
return lambda pl, segment_info, mode: (
'payloads' in segment_info
and channel_name in segment_info['payloads']
and (
isinstance(segment_info['payloads'][channel_name], str)
and segment_info['payloads'][channel_name] == value
) or (
len(segment_info['payloads'][channel_name]) == 2
and segment_info['payloads'][channel_name][0] == value
)
)

View File

@ -16,11 +16,20 @@ def requires_filesystem_watcher(func):
func.powerline_requires_filesystem_watcher = True func.powerline_requires_filesystem_watcher = True
return func return func
def layered_selector(func):
func.powerline_layered_selector = True
return func
def recursive_selector(func):
func.powerline_recursive_selector = True
return func
def new_empty_segment_line(): def new_empty_segment_line():
return { return {
'left': [], 'left': [],
'right': [] 'right': [],
'center': []
} }
@ -91,7 +100,7 @@ class Theme(object):
for segdict in itertools.chain((theme_config['segments'],), for segdict in itertools.chain((theme_config['segments'],),
theme_config['segments'].get('above', ())): theme_config['segments'].get('above', ())):
self.segments.append(new_empty_segment_line()) self.segments.append(new_empty_segment_line())
for side in ['left', 'right']: for side in ['left', 'right', 'center']:
for segment in segdict.get(side, []): for segment in segdict.get(side, []):
segment = get_segment(segment, side) segment = get_segment(segment, side)
if segment: if segment:
@ -133,7 +142,7 @@ class Theme(object):
Line number for which segments should be obtained. Is counted from Line number for which segments should be obtained. Is counted from
zero (botmost line). zero (botmost line).
''' '''
for side in [side] if side else ['left', 'right']: for side in [side] if side else ['left', 'right', 'center']:
parsed_segments = [] parsed_segments = []
for segment in self.segments[line][side]: for segment in self.segments[line][side]:
if segment['display_condition'](self.pl, segment_info, mode): if segment['display_condition'](self.pl, segment_info, mode):