diff --git a/powerline/__init__.py b/powerline/__init__.py index a50b939f..6cdce677 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -901,6 +901,23 @@ class Powerline(object): exc = e 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): '''Setup the environment to use powerline. diff --git a/powerline/colorscheme.py b/powerline/colorscheme.py index 66416b55..928c6157 100644 --- a/powerline/colorscheme.py +++ b/powerline/colorscheme.py @@ -4,13 +4,13 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct from copy import copy from powerline.lib.unicode import unicode - +from colorsys import hsv_to_rgb, rgb_to_hsv DEFAULT_MODE_KEY = None ATTR_BOLD = 1 ATTR_ITALIC = 2 ATTR_UNDERLINE = 4 - +ATTR_OVERLINE = 8 def get_attrs_flag(attrs): '''Convert an attribute array to a renderer flag.''' @@ -21,47 +21,99 @@ def get_attrs_flag(attrs): attrs_flag |= ATTR_ITALIC if 'underline' in attrs: attrs_flag |= ATTR_UNDERLINE + if 'overline' in attrs: + attrs_flag |= ATTR_OVERLINE 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. 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): def __init__(self, colorscheme_config, colors_config): '''Initialize a colorscheme.''' self.colors = {} self.gradients = {} + self.gradient_types = {} self.groups = colorscheme_config['groups'] self.translations = colorscheme_config.get('mode_translations', {}) # Create a dict of color tuples with both a cterm and hex value for color_name, color in colors_config['colors'].items(): - try: - self.colors[color_name] = (color[0], int(color[1], 16)) - except TypeError: + if type(color) == int or type(color) == bool: 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 - # values. Two lists in place of one list of pairs were chosen because + # 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 # true colors allow more precise gradients. for gradient_name, gradient in colors_config['gradients'].items(): - if len(gradient) == 2: - self.gradients[gradient_name] = ( - (gradient[0], [int(color, 16) for color in gradient[1]])) - else: - self.gradients[gradient_name] = ( - (gradient[0], [cterm_to_hex[color] for color in gradient[0]])) + if type(gradient[0]) == list: + if len(gradient) > 1 and type(gradient[1][0]) == float: + 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: + self.gradients[gradient_name] = [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): 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: return self.colors[gradient] @@ -105,14 +157,16 @@ class Colorscheme(object): raise KeyError('Highlighting groups not found in colorscheme: ' + ', '.join(groups)) 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: - 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 { 'fg': pick_color(group_props['fg']), '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 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) + diff --git a/powerline/renderer.py b/powerline/renderer.py index 31aca80e..3989b7e0 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -267,7 +267,7 @@ class Renderer(object): Maximum width text can occupy. May be exceeded if there are too much non-removable segments. :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. :param int line: 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')), '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) @@ -353,23 +357,28 @@ class Renderer(object): divider_widths = self.compute_divider_widths(theme) # 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) current_width = self._render_length(theme, segments, divider_widths) + if current_width > width: for segment in chain(segments_priority, no_priority_segments): if segment['truncate'] is not None: 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) if current_width > width and len(segments) > 100: # When there are too many segments use faster, but less correct # algorithm for width computation - diff = current_width - width for segment in segments_priority: segments.remove(segment) - diff -= segment['_len'] - if diff <= 0: + current_width -= segment['_len'] + if current_width <= width: break current_width = self._render_length(theme, segments, divider_widths) if current_width > width: @@ -409,6 +418,54 @@ class Renderer(object): 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): '''Translate non-printable characters and calculate segment width ''' @@ -421,6 +478,13 @@ class Renderer(object): else: 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): '''Update segments lengths and return them ''' @@ -448,7 +512,7 @@ class Renderer(object): side = segment['side'] segment_len = segment['_contents_len'] if not segment['literal_contents'][1]: - if side == 'left': + if side != 'right': if segment is not last_segment: compare_segment = next(iter(( segment @@ -460,12 +524,16 @@ class Renderer(object): else: 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( segment is first_segment if side == 'left' else 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 draw_divider = segment['draw_' + divider_type + '_divider'] @@ -492,6 +560,7 @@ class Renderer(object): segments_len = len(segments) divider_spaces = theme.get_spaces() prev_segment = theme.EMPTY_SEGMENT + next_segment = theme.EMPTY_SEGMENT try: first_segment = next(iter(( segment @@ -512,23 +581,28 @@ class Renderer(object): for index, segment in enumerate(segments): side = segment['side'] 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 + if segment is not last_segment: + compare_segment = next(iter(( + segment + for segment in segments[index + 1:] + if not segment['literal_contents'][1] + ))) + else: + next_segment = theme.EMPTY_SEGMENT + if side != 'right': + compare_segment = next_segment else: compare_segment = prev_segment outer_padding = int(bool( segment is first_segment if side == 'left' else 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 * ' ' - 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 = '' contents_raw = segment['contents'] @@ -541,9 +615,9 @@ class Renderer(object): # 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: + if draw_divider and (side != 'center' or segment != last_segment): divider_raw = self.escape(theme.get_divider(side, divider_type)) - if side == 'left': + if side != 'right': contents_raw = outer_padding + contents_raw + (divider_spaces * ' ') else: contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding @@ -556,25 +630,31 @@ class Renderer(object): divider_fg = segment['highlight']['bg'] divider_bg = compare_segment['highlight']['bg'] - if side == 'left': + if side != 'right': 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) 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, **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_hl'] = divider_highlighted + contents_highlighted else: - if side == 'left': + if side == 'left' or (side == 'center' and segment != last_segment): contents_raw = outer_padding + contents_raw else: 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_hl'] = contents_highlighted prev_segment = segment diff --git a/powerline/segment.py b/powerline/segment.py index c83bf6f2..b5bcaae8 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -2,6 +2,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) 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): @@ -181,7 +182,7 @@ def process_segment(pl, side, segment_info, parsed_segments, segment, mode, colo return if isinstance(contents, list): - # Needs copying here, but it was performed at the very start of the + # Needs copying here, but it was performed at the very start of the # function segment_base = segment if contents: @@ -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) 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: 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 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: - pl.error('Failed to get segment selector, ignoring it') + pl.error('Failed to get segment selector "{0}", ignoring it'.format(function_name)) return function 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, 'before': 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( pl, segment_info, parsed_segments, side, mode, colorscheme, patcher_args=args, subsegments=subsegments, lister=_contents_func, + **opt ) ), '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 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: - 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: startup_func = None shutdown_func = None diff --git a/powerline/selectors/__init__.py b/powerline/selectors/__init__.py index e69de29b..4795b9a2 100644 --- a/powerline/selectors/__init__.py +++ b/powerline/selectors/__init__.py @@ -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]) + ) + diff --git a/powerline/selectors/common.py b/powerline/selectors/common.py new file mode 100644 index 00000000..0f698cce --- /dev/null +++ b/powerline/selectors/common.py @@ -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 diff --git a/powerline/selectors/i3wm.py b/powerline/selectors/i3wm.py new file mode 100644 index 00000000..73b78c0d --- /dev/null +++ b/powerline/selectors/i3wm.py @@ -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 + ) + ) diff --git a/powerline/theme.py b/powerline/theme.py index b3a23a1f..ca6550cc 100644 --- a/powerline/theme.py +++ b/powerline/theme.py @@ -16,11 +16,20 @@ def requires_filesystem_watcher(func): func.powerline_requires_filesystem_watcher = True 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(): return { 'left': [], - 'right': [] + 'right': [], + 'center': [] } @@ -91,7 +100,7 @@ class Theme(object): for segdict in itertools.chain((theme_config['segments'],), theme_config['segments'].get('above', ())): self.segments.append(new_empty_segment_line()) - for side in ['left', 'right']: + for side in ['left', 'right', 'center']: for segment in segdict.get(side, []): segment = get_segment(segment, side) if segment: @@ -130,10 +139,10 @@ class Theme(object): and ljust/rjust properties applied. :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 zero (botmost line). ''' - for side in [side] if side else ['left', 'right']: + for side in [side] if side else ['left', 'right', 'center']: parsed_segments = [] for segment in self.segments[line][side]: if segment['display_condition'](self.pl, segment_info, mode):