Refactor segment rendering
This commit introduces the following changes to themes and segment rendering: - Spacer segments are now regular string/function type segments with "width": "auto" in the themes. - The "rjust"/"ljust" properties have been replaced by the "width" option combined with a new "align" option. - Renderer._render_segments() is now a generator which renders each segment separately, and assigns the rendered contents to "_rendered_hl" and "_rendered_raw" in the segment dict. - Renderer.render() returns the segments by joining the "_rendered_hl" values for each segment. - Spacer segment widths are calculated in the render() method, and assigned to "_space_left" and "_space_right" in the segment dict. These spaces are then applied in Renderer._render_segments(). - All space characters are converted to no-break spaces (U+00A0) in the "_rendered_hl" property. Refs #113. Refs #154.
This commit is contained in:
parent
cb860ce5d0
commit
bfdb7f8028
|
@ -237,12 +237,6 @@ Themes
|
|||
highlighting group is defined in the :ref:`highlight_group
|
||||
option <config-themes-seg-highlight_group>`.
|
||||
|
||||
``filler``
|
||||
If the statusline is rendered with a specific width, remaining
|
||||
whitespace is distributed among filler segments. The
|
||||
highlighting group is defined in the :ref:`highlight_group
|
||||
option <config-themes-seg-highlight_group>`.
|
||||
|
||||
``module``
|
||||
.. _config-themes-seg-module:
|
||||
|
||||
|
@ -276,13 +270,18 @@ Themes
|
|||
``args``
|
||||
A dict of arguments to be passed to a ``function`` segment.
|
||||
|
||||
``ljust``
|
||||
If set, the segment will be left justified to the width specified by
|
||||
this option.
|
||||
``align``
|
||||
Aligns the segments contents to the left (``l``), center (``c``) or
|
||||
right (``r``).
|
||||
|
||||
``rjust``
|
||||
If set, the segment will be right justified to the width specified
|
||||
by this option.
|
||||
``width``
|
||||
Enforces a specific width for this segment.
|
||||
|
||||
This segment will work as a spacer if the width is set to ``auto``.
|
||||
Several spacers may be used, and the space will be distributed
|
||||
equally among all the spacer segments. Spacers may have contents,
|
||||
either returned by a function or a static string, and the contents
|
||||
can be aligned with the ``align`` property.
|
||||
|
||||
``priority``
|
||||
Optional segment priority. Segments with priority ``-1`` (the
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
"highlight_group": ["file_name"]
|
||||
},
|
||||
{
|
||||
"type": "filler",
|
||||
"highlight_group": ["background"]
|
||||
"type": "string",
|
||||
"highlight_group": ["background"],
|
||||
"draw_divider": false,
|
||||
"width": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -41,8 +41,10 @@
|
|||
"before": " "
|
||||
},
|
||||
{
|
||||
"type": "filler",
|
||||
"highlight_group": ["background"]
|
||||
"type": "string",
|
||||
"highlight_group": ["background"],
|
||||
"draw_divider": false,
|
||||
"width": "auto"
|
||||
}
|
||||
],
|
||||
"right": [
|
||||
|
@ -70,7 +72,8 @@
|
|||
"args": { "gradient": true },
|
||||
"priority": 30,
|
||||
"after": "%",
|
||||
"rjust": 4
|
||||
"width": 4,
|
||||
"align": "r"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -80,14 +83,16 @@
|
|||
{
|
||||
"name": "line_current",
|
||||
"draw_divider": false,
|
||||
"rjust": 3
|
||||
"width": 3,
|
||||
"align": "r"
|
||||
},
|
||||
{
|
||||
"name": "col_current",
|
||||
"draw_divider": false,
|
||||
"priority": 30,
|
||||
"before": ":",
|
||||
"ljust": 3
|
||||
"width": 3,
|
||||
"align": "l"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
"draw_divider": false
|
||||
},
|
||||
{
|
||||
"type": "filler",
|
||||
"highlight_group": ["background"]
|
||||
"type": "string",
|
||||
"highlight_group": ["background"],
|
||||
"draw_divider": false,
|
||||
"width": "auto"
|
||||
}
|
||||
],
|
||||
"right": [
|
||||
|
@ -16,7 +18,8 @@
|
|||
"args": { "gradient": true },
|
||||
"priority": 30,
|
||||
"after": "%",
|
||||
"rjust": 4
|
||||
"width": 4,
|
||||
"align": "r"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -26,7 +29,8 @@
|
|||
{
|
||||
"name": "line_current",
|
||||
"draw_divider": false,
|
||||
"rjust": 3
|
||||
"width": 3,
|
||||
"align": "r"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ class Renderer(object):
|
|||
|
||||
TERM_24BIT_COLORS = False
|
||||
|
||||
PADDING_CHAR = u'\u00a0' # No-break space
|
||||
|
||||
def __init__(self, theme_config, local_themes, theme_kwargs, term_24bit_colors=False):
|
||||
self.theme = Theme(theme_config=theme_config, **theme_kwargs)
|
||||
self.local_themes = local_themes
|
||||
|
@ -34,13 +32,6 @@ class Renderer(object):
|
|||
else:
|
||||
return self.theme
|
||||
|
||||
@staticmethod
|
||||
def _returned_value(rendered_highlighted, segments, output_raw):
|
||||
if output_raw:
|
||||
return rendered_highlighted, ''.join((segment['rendered_raw'] for segment in segments))
|
||||
else:
|
||||
return rendered_highlighted
|
||||
|
||||
def render(self, mode=None, width=None, theme=None, segments=None, side=None, output_raw=False):
|
||||
'''Render all segments.
|
||||
|
||||
|
@ -56,31 +47,37 @@ class Renderer(object):
|
|||
# Handle excluded/included segments for the current mode
|
||||
segments = [segment for segment in segments\
|
||||
if mode not in segment['exclude_modes'] or (segment['include_modes'] and segment in segment['include_modes'])]
|
||||
rendered_highlighted = self._render_segments(mode, theme, segments)
|
||||
|
||||
segments = [segment for segment in self._render_segments(mode, theme, segments)]
|
||||
|
||||
if not width:
|
||||
# No width specified, so we don't need to crop or pad anything
|
||||
return self._returned_value(rendered_highlighted, segments, output_raw)
|
||||
return self._returned_value(u''.join([segment['_rendered_hl'] for segment in segments]) + self.hl(), segments, output_raw)
|
||||
|
||||
# Create an ordered list of segments that can be dropped
|
||||
segments_priority = [segment for segment in sorted(segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0]
|
||||
while self._total_len(segments) > width and len(segments_priority):
|
||||
while sum([segment['_len'] for segment in segments]) > width and len(segments_priority):
|
||||
segments.remove(segments_priority[0])
|
||||
segments_priority.pop(0)
|
||||
|
||||
# Do another render pass so we can calculate the correct amount of filler space
|
||||
self._render_segments(mode, theme, segments, render_highlighted=False)
|
||||
# Distribute the remaining space on spacer segments
|
||||
segments_spacers = [segment for segment in segments if segment['width'] == 'auto']
|
||||
if segments_spacers:
|
||||
distribute_len, distribute_len_remainder = divmod(width - sum([segment['_len'] for segment in segments]), 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
|
||||
|
||||
# Distribute the remaining space on the filler segments
|
||||
segments_fillers = [segment for segment in segments if segment['type'] == 'filler']
|
||||
if segments_fillers:
|
||||
segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len(segments)), len(segments_fillers))
|
||||
segments_fillers_contents = self.PADDING_CHAR * segments_fillers_len
|
||||
for segment in segments_fillers:
|
||||
segment['contents'] = segments_fillers_contents
|
||||
# Add remainder whitespace to the first filler segment
|
||||
segments_fillers[0]['contents'] += self.PADDING_CHAR * segments_fillers_remainder
|
||||
rendered_highlighted = u''.join([segment['_rendered_hl'] for segment in self._render_segments(mode, theme, segments)]) + self.hl()
|
||||
|
||||
return self._returned_value(self._render_segments(mode, theme, segments), segments, output_raw)
|
||||
return self._returned_value(rendered_highlighted, segments, output_raw)
|
||||
|
||||
def _render_segments(self, mode, theme, segments, render_highlighted=True):
|
||||
'''Internal segment rendering method.
|
||||
|
@ -93,19 +90,20 @@ class Renderer(object):
|
|||
highlighting strings added), and only renders the highlighted
|
||||
statusline if render_highlighted is True.
|
||||
'''
|
||||
rendered_highlighted = u''
|
||||
segments_len = len(segments)
|
||||
try:
|
||||
mode = mode if mode in segments[0]['highlight'] else Colorscheme.DEFAULT_MODE_KEY
|
||||
except IndexError:
|
||||
return ''
|
||||
pass
|
||||
|
||||
for index, segment in enumerate(segments):
|
||||
segment['_rendered_raw'] = u''
|
||||
segment['_rendered_hl'] = u''
|
||||
|
||||
prev_segment = segments[index - 1] if index > 0 else theme.EMPTY_SEGMENT
|
||||
next_segment = segments[index + 1] if index < segments_len - 1 else theme.EMPTY_SEGMENT
|
||||
compare_segment = next_segment if segment['side'] == 'left' else prev_segment
|
||||
segment['rendered_raw'] = u''
|
||||
outer_padding = self.PADDING_CHAR if (index == 0 and segment['side'] == 'left') or (index == segments_len - 1 and segment['side'] == 'right') else ''
|
||||
outer_padding = ' ' if (index == 0 and segment['side'] == 'left') or (index == segments_len - 1 and segment['side'] == 'right') else ''
|
||||
divider_type = 'soft' if compare_segment['highlight'][mode]['bg'] == segment['highlight'][mode]['bg'] else 'hard'
|
||||
|
||||
divider_raw = theme.get_divider(segment['side'], divider_type)
|
||||
|
@ -114,22 +112,18 @@ class Renderer(object):
|
|||
contents_highlighted = ''
|
||||
|
||||
# Pad segments first
|
||||
if segment['type'] == 'filler':
|
||||
pass
|
||||
elif segment['draw_divider'] or divider_type == 'hard':
|
||||
if segment['draw_divider'] or (divider_type == 'hard' and segment['width'] != 'auto'):
|
||||
if segment['side'] == 'left':
|
||||
contents_raw = outer_padding + contents_raw + self.PADDING_CHAR
|
||||
divider_raw = divider_raw + self.PADDING_CHAR
|
||||
contents_raw = outer_padding + (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') + ' '
|
||||
divider_raw = divider_raw + ' '
|
||||
else:
|
||||
contents_raw = ' ' + (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') + outer_padding
|
||||
divider_raw = ' ' + divider_raw
|
||||
else:
|
||||
contents_raw = self.PADDING_CHAR + contents_raw + outer_padding
|
||||
divider_raw = self.PADDING_CHAR + divider_raw
|
||||
elif contents_raw:
|
||||
if segment['side'] == 'left':
|
||||
contents_raw = outer_padding + contents_raw
|
||||
contents_raw = outer_padding + (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ')
|
||||
else:
|
||||
contents_raw = contents_raw + outer_padding
|
||||
else:
|
||||
raise ValueError('Unknown segment type')
|
||||
contents_raw = (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') + outer_padding
|
||||
|
||||
# Apply highlighting to padded dividers and contents
|
||||
if render_highlighted:
|
||||
|
@ -144,37 +138,31 @@ class Renderer(object):
|
|||
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'][mode])
|
||||
|
||||
# Append padded raw and highlighted segments to the rendered segment variables
|
||||
if segment['type'] == 'filler':
|
||||
rendered_highlighted += contents_highlighted if contents_raw else ''
|
||||
elif segment['draw_divider'] or divider_type == 'hard':
|
||||
# Draw divider if specified, or if it's a hard divider
|
||||
# Note: Hard dividers are always drawn, regardless of
|
||||
# the draw_divider option
|
||||
if segment['draw_divider'] or (divider_type == 'hard' and segment['width'] != 'auto'):
|
||||
if segment['side'] == 'left':
|
||||
segment['rendered_raw'] += contents_raw + divider_raw
|
||||
rendered_highlighted += contents_highlighted + divider_highlighted
|
||||
segment['_rendered_raw'] += contents_raw + divider_raw
|
||||
segment['_rendered_hl'] += contents_highlighted + divider_highlighted
|
||||
else:
|
||||
segment['_rendered_raw'] += divider_raw + contents_raw
|
||||
segment['_rendered_hl'] += divider_highlighted + contents_highlighted
|
||||
else:
|
||||
segment['rendered_raw'] += divider_raw + contents_raw
|
||||
rendered_highlighted += divider_highlighted + contents_highlighted
|
||||
elif contents_raw:
|
||||
# Segments without divider
|
||||
if segment['side'] == 'left':
|
||||
segment['rendered_raw'] += contents_raw
|
||||
rendered_highlighted += contents_highlighted
|
||||
segment['_rendered_raw'] += contents_raw
|
||||
segment['_rendered_hl'] += contents_highlighted
|
||||
else:
|
||||
segment['rendered_raw'] += contents_raw
|
||||
rendered_highlighted += contents_highlighted
|
||||
rendered_highlighted += self.hl()
|
||||
return rendered_highlighted
|
||||
segment['_rendered_raw'] += contents_raw
|
||||
segment['_rendered_hl'] += contents_highlighted
|
||||
segment['_len'] = len(segment['_rendered_raw'])
|
||||
# Replace rendered spaces with no-break spaces
|
||||
segment['_rendered_hl'] = segment['_rendered_hl'].replace(' ', u'\u00a0')
|
||||
yield segment
|
||||
|
||||
@staticmethod
|
||||
def _total_len(segments):
|
||||
'''Return total/rendered length of all segments.
|
||||
|
||||
This method uses the rendered_raw property of the segments and requires
|
||||
that the segments have been rendered using the render() method first.
|
||||
'''
|
||||
return len(''.join([segment['rendered_raw'] for segment in segments]))
|
||||
def _returned_value(rendered_highlighted, segments, output_raw):
|
||||
if output_raw:
|
||||
return rendered_highlighted, ''.join((segment['_rendered_raw'] for segment in segments))
|
||||
else:
|
||||
return rendered_highlighted
|
||||
|
||||
@staticmethod
|
||||
def escape(string):
|
||||
|
|
|
@ -38,6 +38,9 @@ class VimRenderer(Renderer):
|
|||
else:
|
||||
mode = 'nc'
|
||||
theme, segments = self.window_cache.get(window_id, (None, None))
|
||||
for segment in segments:
|
||||
segment['_space_left'] = 0
|
||||
segment['_space_right'] = 0
|
||||
statusline = super(VimRenderer, self).render(mode, winwidth, theme, segments)
|
||||
return statusline
|
||||
|
||||
|
|
|
@ -45,11 +45,16 @@ class Segment(object):
|
|||
'contents_func': contents_func,
|
||||
'contents': contents,
|
||||
'args': segment.get('args', {}),
|
||||
'ljust': segment.get('ljust', False),
|
||||
'rjust': segment.get('rjust', False),
|
||||
'priority': segment.get('priority', -1),
|
||||
'draw_divider': segment.get('draw_divider', True),
|
||||
'side': side,
|
||||
'exclude_modes': segment.get('exclude_modes', []),
|
||||
'include_modes': segment.get('include_modes', []),
|
||||
'width': segment.get('width'),
|
||||
'align': segment.get('align', 'l'),
|
||||
'_rendered_raw': u'',
|
||||
'_rendered_hl': u'',
|
||||
'_len': 0,
|
||||
'_space_left': 0,
|
||||
'_space_right': 0,
|
||||
}
|
||||
|
|
|
@ -65,15 +65,21 @@ class Theme(object):
|
|||
else:
|
||||
segment['contents'] = contents
|
||||
parsed_segments.append(segment)
|
||||
elif segment['type'] == 'filler' or (segment['type'] == 'string' and segment['contents'] is not None):
|
||||
elif segment['width'] == 'auto' or (segment['type'] == 'string' and segment['contents'] is not None):
|
||||
parsed_segments.append(segment)
|
||||
else:
|
||||
continue
|
||||
for segment in parsed_segments:
|
||||
segment = self.add_highlight(segment)
|
||||
segment['contents'] = (segment['before'] + unicode(segment['contents']) + segment['after'])\
|
||||
.ljust(segment['ljust'])\
|
||||
.rjust(segment['rjust'])
|
||||
segment['contents'] = segment['before'] + unicode(segment['contents'] if segment['contents'] is not None else '') + segment['after']
|
||||
# Align segment contents
|
||||
if segment['width'] and segment['width'] != 'auto':
|
||||
if segment['align'] == 'l':
|
||||
segment['contents'] = segment['contents'].ljust(segment['width'])
|
||||
elif segment['align'] == 'r':
|
||||
segment['contents'] = segment['contents'].rjust(segment['width'])
|
||||
elif segment['align'] == 'c':
|
||||
segment['contents'] = segment['contents'].center(segment['width'])
|
||||
# We need to yield a copy of the segment, or else mode-dependent
|
||||
# segment contents can't be cached correctly e.g. when caching
|
||||
# non-current window contents for vim statuslines
|
||||
|
|
Loading…
Reference in New Issue