Merge pull request #1043 from ZyX-I/truncate-expand
Add segment.truncate and segment.expand attributes support
This commit is contained in:
commit
a2b58370c4
|
@ -369,6 +369,8 @@ ascii Theme without any unicode characters at all
|
|||
|
||||
Each segment dictionary has the following options:
|
||||
|
||||
.. _config-themes-seg-type:
|
||||
|
||||
``type``
|
||||
The segment type. Can be one of ``function`` (default), ``string`` or
|
||||
``segments_list``:
|
||||
|
@ -439,9 +441,13 @@ ascii Theme without any unicode characters at all
|
|||
``args``
|
||||
A dict of arguments to be passed to a ``function`` segment.
|
||||
|
||||
.. _config-themes-seg-align:
|
||||
|
||||
``align``
|
||||
Aligns the segments contents to the left (``l``), center (``c``) or
|
||||
right (``r``).
|
||||
right (``r``). Has no sense if ``width`` key was not specified or if
|
||||
segment provides its own function for ``auto`` ``width`` handling and
|
||||
does not care about this option.
|
||||
|
||||
.. _config-themes-seg-width:
|
||||
|
||||
|
@ -454,6 +460,8 @@ ascii Theme without any unicode characters at all
|
|||
either returned by a function or a static string, and the contents
|
||||
can be aligned with the ``align`` property.
|
||||
|
||||
.. _config-themes-seg-priority:
|
||||
|
||||
``priority``
|
||||
Optional segment priority. Segments with priority ``None`` (the default
|
||||
priority, represented by ``null`` in json) will always be included,
|
||||
|
@ -482,6 +490,8 @@ ascii Theme without any unicode characters at all
|
|||
segments. Only applicable for functions returning multiple segments.
|
||||
Defaults to ``False``.
|
||||
|
||||
.. _config-themes-seg-exclude_modes:
|
||||
|
||||
``exclude_modes``
|
||||
A list of modes where this segment will be excluded: The segment is
|
||||
included in all modes, *except* for the modes in this list.
|
||||
|
|
|
@ -36,8 +36,11 @@ Listers must return a sequence of pairs. First item in the pair must contain
|
|||
a ``segment_info`` dictionary specific to one of the listed entities.
|
||||
|
||||
Second item must contain another dictionary: it will be used to modify the
|
||||
resulting segment. In addition to usual keys that describe segment the following
|
||||
keys may be present (it is advised that *only* the following keys will be used):
|
||||
resulting segment. In addition to :ref:`usual keys that describe segment
|
||||
<dev-segments-segment>` the following keys may be present (it is advised that
|
||||
*only* the following keys will be used):
|
||||
|
||||
.. _dev-listers-mode:
|
||||
|
||||
``mode``
|
||||
Segment-specific mode. Used to alter segment highlighting.
|
||||
|
|
|
@ -51,6 +51,8 @@ powerline:
|
|||
theme-specific values go directly to :ref:`top-level themes
|
||||
<config-themes>`.
|
||||
|
||||
.. _dev-segments-startup:
|
||||
|
||||
``startup``
|
||||
This attribute must be a callable which accepts the following keyword
|
||||
arguments:
|
||||
|
@ -68,6 +70,8 @@ powerline:
|
|||
used (more specific: when :py:class:`powerline.Powerline` constructor
|
||||
received true ``run_once`` argument).
|
||||
|
||||
.. _dev-segments-shutdown:
|
||||
|
||||
``shutdown``
|
||||
This attribute must be a callable that accepts no arguments and shuts down
|
||||
threads and frees any other resources allocated in ``startup`` method of the
|
||||
|
@ -75,6 +79,39 @@ powerline:
|
|||
|
||||
This function is not called when ``startup`` method is not called.
|
||||
|
||||
.. _dev-segments-expand:
|
||||
|
||||
``expand``
|
||||
This attribute must be a callable that accepts the following keyword
|
||||
arguments:
|
||||
|
||||
* ``pl``: :py:class:`powerline.PowerlineLogger` instance which is to be used
|
||||
for logging.
|
||||
* ``amount``: integer number representing amount of display cells result
|
||||
must occupy.
|
||||
|
||||
.. warning::
|
||||
“Amount of display cells” is *not* number of Unicode codepoints, string
|
||||
length, or byte count. It is suggested that your function should look
|
||||
something like ``return (' ' * amount) + segment['contents']`` where
|
||||
``' '`` may be replaced with anything that is known to occupy exactly
|
||||
one display cell.
|
||||
* ``segment``: :ref:`segment dictionary <dev-segments-segment>`.
|
||||
* Any arguments found in user configuration for the given segment (i.e.
|
||||
:ref:`args key <config-themes-seg-args>`).
|
||||
|
||||
It must return new value of :ref:`contents <dev-segments-seg-contents>` key.
|
||||
|
||||
.. _dev-segments-truncate:
|
||||
|
||||
``truncate``
|
||||
Like :ref:`expand function <dev-segments-expand>`, but for truncating
|
||||
segments. Here ``amount`` means the number of display cells which must be
|
||||
freed.
|
||||
|
||||
This function is called for all segments before powerline starts purging
|
||||
them to free space.
|
||||
|
||||
This callable object should may return either a string (``unicode`` in Python2
|
||||
or ``str`` in Python3, *not* ``str`` in Python2 or ``bytes`` in Python3) object
|
||||
or a list of dictionaries. String object is a short form of the following return
|
||||
|
@ -87,6 +124,8 @@ value:
|
|||
'highlight_group': [segment_name],
|
||||
}]
|
||||
|
||||
.. _dev-segments-return:
|
||||
|
||||
Returned list is a list of segments treated independently, except for
|
||||
:ref:`draw_inner_divider key <dev-segments-draw_inner_divider>`.
|
||||
|
||||
|
@ -158,6 +197,87 @@ Detailed description of used dictionary keys:
|
|||
No error occurs if segment has this key, but no used highlight groups use
|
||||
gradient color.
|
||||
|
||||
``_*``
|
||||
Keys starting with underscore are reserved for powerline and must not be
|
||||
returned.
|
||||
|
||||
``__*``
|
||||
Keys starting with two underscores are reserved for the segment functions,
|
||||
specifically for :ref:`expand function <dev-segments-expand>`.
|
||||
|
||||
.. _dev-segments-segment:
|
||||
|
||||
Segment dictionary
|
||||
==================
|
||||
|
||||
Segment dictionary contains the following keys:
|
||||
|
||||
* All keys returned by segment function (if it was used).
|
||||
|
||||
* All of the following keys:
|
||||
|
||||
``name``
|
||||
Segment name: value of the :ref:`name key <config-themes-seg-name>` or
|
||||
function name (last component of the :ref:`function key
|
||||
<config-themes-seg-function>`). May be ``None``.
|
||||
|
||||
``type``
|
||||
:ref:`Segment type <config-themes-seg-type>`. Always represents actual type
|
||||
and is never ``None``.
|
||||
|
||||
``highlight_group``, ``divider_highlight_group``
|
||||
Used highlight groups. May be ``None``.
|
||||
|
||||
.. _dev-segments-seg-around:
|
||||
|
||||
``before``, ``after``
|
||||
Value of :ref:`before <config-themes-seg-before>` or :ref:`after
|
||||
<config-themes-seg-after>` configuration options. May be ``None`` as well as
|
||||
an empty string.
|
||||
|
||||
``contents_func``
|
||||
Function used to get segment contents. May be ``None``.
|
||||
|
||||
.. _dev-segments-seg-contents:
|
||||
|
||||
``contents``
|
||||
Actual segment contents, excluding dividers and :ref:`before/after
|
||||
<dev-segments-seg-around>`. May be ``None``.
|
||||
|
||||
``priority``
|
||||
:ref:`Segment priority <config-themes-seg-priority>`. May be ``None`` for no
|
||||
priority (such segments are always shown).
|
||||
|
||||
``draw_soft_divider``, ``draw_hard_divider``, ``draw_inner_divider``
|
||||
:ref:`Divider control flags <dev-segments-draw_inner_divider>`.
|
||||
|
||||
``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``.
|
||||
|
||||
``width``, ``align``
|
||||
:ref:`Width and align options <config-themes-seg-align>`. May be ``None``.
|
||||
|
||||
``expand``, ``truncate``
|
||||
Partially applied :ref:`expand <dev-segments-expand>` or :ref:`truncate
|
||||
<dev-segments-truncate>` function. Accepts ``pl``, ``amount`` and
|
||||
``segment`` positional parameters, keyword parameters from :ref:`args
|
||||
<config-themes-seg-args>` key were applied.
|
||||
|
||||
``startup``
|
||||
Partially applied :ref:`startup function <dev-segments-startup>`. Accepts
|
||||
``pl`` and ``shutdown_event`` positional parameters, keyword parameters from
|
||||
:ref:`args <config-themes-seg-args>` key were applied.
|
||||
|
||||
``shutdown``
|
||||
:ref:`Shutdown function <dev-segments-shutdown>`. Accepts no argument.
|
||||
|
||||
``mode``
|
||||
:ref:`Segment-specific mode <dev-listers-mode>`. May be ``None``.
|
||||
|
||||
Segments layout
|
||||
===============
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
# vim:fileencoding=utf-8:noet
|
||||
|
||||
from powerline.theme import Theme
|
||||
from unicodedata import east_asian_width, combining
|
||||
import os
|
||||
|
||||
from unicodedata import east_asian_width, combining
|
||||
from itertools import chain
|
||||
|
||||
from powerline.theme import Theme
|
||||
|
||||
try:
|
||||
NBSP = unicode(' ', 'utf-8')
|
||||
except NameError:
|
||||
|
@ -262,14 +265,20 @@ class Renderer(object):
|
|||
|
||||
# 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)
|
||||
for segment in segments_priority:
|
||||
current_width = self._render_length(theme, segments, divider_widths)
|
||||
if current_width <= width:
|
||||
break
|
||||
segments.remove(segment)
|
||||
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)
|
||||
for segment in segments_priority:
|
||||
if current_width <= width:
|
||||
break
|
||||
segments.remove(segment)
|
||||
current_width = self._render_length(theme, segments, divider_widths)
|
||||
|
||||
# Distribute the remaining space on spacer segments
|
||||
segments_spacers = [segment for segment in segments if segment['width'] == 'auto']
|
||||
segments_spacers = [segment for segment in segments if segment['expand'] is not None]
|
||||
if segments_spacers:
|
||||
if not segments_priority:
|
||||
# Update segment['_len'] and current_width if not already done
|
||||
|
@ -278,15 +287,12 @@ class Renderer(object):
|
|||
current_width = self._render_length(theme, segments, divider_widths)
|
||||
distribute_len, distribute_len_remainder = divmod(width - current_width, len(segments_spacers))
|
||||
for segment in segments_spacers:
|
||||
if segment['align'] == 'l':
|
||||
segment['_space_right'] += distribute_len
|
||||
elif segment['align'] == 'r':
|
||||
segment['_space_left'] += distribute_len
|
||||
elif segment['align'] == 'c':
|
||||
space_side, space_side_remainder = divmod(distribute_len, 2)
|
||||
segment['_space_left'] += space_side + space_side_remainder
|
||||
segment['_space_right'] += space_side
|
||||
segments_spacers[0]['_space_right'] += distribute_len_remainder
|
||||
segment['contents'] = (
|
||||
segment['expand'](
|
||||
self.pl,
|
||||
distribute_len + (1 if distribute_len_remainder > 0 else 0),
|
||||
segment))
|
||||
distribute_len_remainder -= 1
|
||||
# `_len` key is not needed anymore, but current_width should have an
|
||||
# actual value for various bindings.
|
||||
current_width = width
|
||||
|
@ -321,7 +327,7 @@ class Renderer(object):
|
|||
))
|
||||
|
||||
draw_divider = segment['draw_' + divider_type + '_divider']
|
||||
segment_len += segment['_space_left'] + segment['_space_right'] + outer_padding
|
||||
segment_len += outer_padding
|
||||
if draw_divider:
|
||||
segment_len += divider_widths[side][divider_type] + divider_spaces
|
||||
|
||||
|
@ -363,30 +369,14 @@ class Renderer(object):
|
|||
if draw_divider:
|
||||
divider_raw = self.escape(theme.get_divider(side, divider_type))
|
||||
if side == 'left':
|
||||
contents_raw = (
|
||||
outer_padding + (segment['_space_left'] * ' ')
|
||||
+ contents_raw
|
||||
+ ((divider_spaces + segment['_space_right']) * ' ')
|
||||
)
|
||||
contents_raw = outer_padding + contents_raw + (divider_spaces * ' ')
|
||||
else:
|
||||
contents_raw = (
|
||||
((divider_spaces + segment['_space_left']) * ' ')
|
||||
+ contents_raw
|
||||
+ (segment['_space_right'] * ' ') + outer_padding
|
||||
)
|
||||
contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding
|
||||
else:
|
||||
if side == 'left':
|
||||
contents_raw = (
|
||||
outer_padding + (segment['_space_left'] * ' ')
|
||||
+ contents_raw
|
||||
+ (segment['_space_right'] * ' ')
|
||||
)
|
||||
contents_raw = outer_padding + contents_raw
|
||||
else:
|
||||
contents_raw = (
|
||||
(segment['_space_left'] * ' ')
|
||||
+ contents_raw
|
||||
+ (segment['_space_right'] * ' ') + outer_padding
|
||||
)
|
||||
contents_raw = contents_raw + outer_padding
|
||||
|
||||
# Replace spaces with no-break spaces
|
||||
contents_raw = contents_raw.translate(self.np_character_translations)
|
||||
|
|
|
@ -82,14 +82,20 @@ segment_getters = {
|
|||
}
|
||||
|
||||
|
||||
def get_attr_func(contents_func, key, args):
|
||||
def get_attr_func(contents_func, key, args, is_space_func=False):
|
||||
try:
|
||||
func = getattr(contents_func, key)
|
||||
except AttributeError:
|
||||
return None
|
||||
else:
|
||||
if args is None:
|
||||
return lambda: func()
|
||||
if is_space_func:
|
||||
def expand_func(pl, amount, segment):
|
||||
try:
|
||||
return func(pl=pl, amount=amount, segment=segment, **args)
|
||||
except Exception as e:
|
||||
pl.exception('Exception while computing {0} function: {1}', key, str(e))
|
||||
return segment['contents'] + (' ' * amount)
|
||||
return expand_func
|
||||
else:
|
||||
return lambda pl, shutdown_event: func(pl=pl, shutdown_event=shutdown_event, **args)
|
||||
|
||||
|
@ -285,6 +291,8 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
'include_modes': segment.get('include_modes', []),
|
||||
'width': None,
|
||||
'align': None,
|
||||
'expand': None,
|
||||
'truncate': None,
|
||||
'startup': None,
|
||||
'shutdown': None,
|
||||
'mode': None,
|
||||
|
@ -292,13 +300,13 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
'_rendered_hl': '',
|
||||
'_len': None,
|
||||
'_contents_len': None,
|
||||
'_space_left': 0,
|
||||
'_space_right': 0,
|
||||
}
|
||||
|
||||
if segment_type == 'function':
|
||||
startup_func = get_attr_func(_contents_func, 'startup', args)
|
||||
shutdown_func = get_attr_func(_contents_func, 'shutdown', None)
|
||||
shutdown_func = getattr(_contents_func, 'shutdown', None)
|
||||
expand_func = get_attr_func(_contents_func, 'expand', args, True)
|
||||
truncate_func = get_attr_func(_contents_func, 'truncate', args, True)
|
||||
|
||||
if hasattr(_contents_func, 'powerline_requires_filesystem_watcher'):
|
||||
create_watcher = lambda: create_file_watcher(pl, common_config['watcher'])
|
||||
|
@ -312,6 +320,8 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
startup_func = None
|
||||
shutdown_func = None
|
||||
contents_func = None
|
||||
expand_func = None
|
||||
truncate_func = None
|
||||
|
||||
return {
|
||||
'name': name or function_name,
|
||||
|
@ -331,6 +341,8 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
'include_modes': segment.get('include_modes', []),
|
||||
'width': segment.get('width'),
|
||||
'align': segment.get('align', 'l'),
|
||||
'expand': expand_func,
|
||||
'truncate': truncate_func,
|
||||
'startup': startup_func,
|
||||
'shutdown': shutdown_func,
|
||||
'mode': None,
|
||||
|
@ -338,8 +350,6 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, ge
|
|||
'_rendered_hl': '',
|
||||
'_len': None,
|
||||
'_contents_len': None,
|
||||
'_space_left': 0,
|
||||
'_space_right': 0,
|
||||
}
|
||||
|
||||
return get
|
||||
|
|
|
@ -23,6 +23,26 @@ def new_empty_segment_line():
|
|||
}
|
||||
|
||||
|
||||
def add_spaces_left(pl, amount, segment):
|
||||
return (' ' * amount) + segment['contents']
|
||||
|
||||
|
||||
def add_spaces_right(pl, amount, segment):
|
||||
return segment['contents'] + (' ' * amount)
|
||||
|
||||
|
||||
def add_spaces_center(pl, amount, segment):
|
||||
amount, remainder = divmod(amount, 2)
|
||||
return (' ' * (amount + remainder)) + segment['contents'] + (' ' * amount)
|
||||
|
||||
|
||||
expand_functions = {
|
||||
'l': add_spaces_right,
|
||||
'r': add_spaces_left,
|
||||
'c': add_spaces_center,
|
||||
}
|
||||
|
||||
|
||||
class Theme(object):
|
||||
def __init__(self,
|
||||
ext,
|
||||
|
@ -128,6 +148,13 @@ class Theme(object):
|
|||
self.colorscheme,
|
||||
)
|
||||
for segment in parsed_segments:
|
||||
width = segment['width']
|
||||
align = segment['align']
|
||||
if width == 'auto' and segment['expand'] is None:
|
||||
segment['expand'] = expand_functions.get(align)
|
||||
if segment['expand'] is None:
|
||||
self.pl.error('Align argument must be “r”, “l” or “c”, not “{0}”', align)
|
||||
|
||||
segment['contents'] = segment['before'] + u(segment['contents'] if segment['contents'] is not None else '') + segment['after']
|
||||
# Align segment contents
|
||||
if segment['width'] and segment['width'] != 'auto':
|
||||
|
|
|
@ -44,6 +44,7 @@ def urllib_read(query_url):
|
|||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Process(object):
|
||||
def __init__(self, output, err):
|
||||
self.output = output
|
||||
|
|
|
@ -94,7 +94,7 @@ config = {
|
|||
highlighted_string('g', 'str2'),
|
||||
],
|
||||
'right': [
|
||||
highlighted_string('f', 'str2', width='auto', align='right'),
|
||||
highlighted_string('f', 'str2', width='auto', align='r'),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -474,6 +474,45 @@ class TestSegmentAttributes(TestRender):
|
|||
}
|
||||
self.assertRenderEqual(p, '{56} pl;{6-}>>{--}')
|
||||
|
||||
@add_args
|
||||
def test_expand(self, p, config):
|
||||
def m1(divider=',', **kwargs):
|
||||
return divider.join(kwargs.keys()) + divider
|
||||
|
||||
def expand(pl, amount, segment, **kwargs):
|
||||
return ('-' * amount) + segment['contents']
|
||||
|
||||
m1.expand = expand
|
||||
sys.modules['bar'] = Args(m1=m1)
|
||||
config['themes/test/default']['segments'] = {
|
||||
'left': [
|
||||
{
|
||||
'function': 'bar.m1',
|
||||
'width': 'auto'
|
||||
}
|
||||
]
|
||||
}
|
||||
self.assertRenderEqual(p, '{56} ----pl,{6-}>>{--}', width=10)
|
||||
|
||||
@add_args
|
||||
def test_truncate(self, p, config):
|
||||
def m1(divider=',', **kwargs):
|
||||
return divider.join(kwargs.keys()) + divider
|
||||
|
||||
def truncate(pl, amount, segment, **kwargs):
|
||||
return segment['contents'][:-amount]
|
||||
|
||||
m1.truncate = truncate
|
||||
sys.modules['bar'] = Args(m1=m1)
|
||||
config['themes/test/default']['segments'] = {
|
||||
'left': [
|
||||
{
|
||||
'function': 'bar.m1'
|
||||
}
|
||||
]
|
||||
}
|
||||
self.assertRenderEqual(p, '{56} p{6-}>>{--}', width=4)
|
||||
|
||||
|
||||
class TestSegmentData(TestRender):
|
||||
@add_args
|
||||
|
|
Loading…
Reference in New Issue