Fix renderer length computation

Current sum() of once computed _len’s is completely inappropriate in case
removal of the segment caused change in divider lengths: addition or removal of
dividers or change of the divider type when dividers have different length.

Also contains some optimizations: first of all _render_segments is called only
once always, same for strwidth() function for each string. Space is considired
to always have length 1. And do not bother computing any length if no width was
specified.
This commit is contained in:
ZyX 2014-08-03 11:56:41 +04:00
parent 3148acfef1
commit 0403f7af1a

View File

@ -222,20 +222,44 @@ class Renderer(object):
segments = theme.get_segments(side, line, self.get_segment_info(segment_info, mode)) segments = theme.get_segments(side, line, self.get_segment_info(segment_info, mode))
# Handle excluded/included segments for the current mode # Handle excluded/included segments for the current mode
segments = [self._get_highlighting(segment, segment['mode'] or mode) for segment in segments segments = [
if mode not in segment['exclude_modes'] and (not segment['include_modes'] or mode in segment['include_modes'])] self._get_highlighting(segment, segment['mode'] or mode)
for segment in segments
segments = [segment for segment in self._render_segments(theme, segments)] if (
mode not in segment['exclude_modes']
and (
not segment['include_modes']
or mode in segment['include_modes']
)
)
]
if not width: if not width:
# No width specified, so we don't need to crop or pad anything # No width specified, so we don't need to crop or pad anything
return construct_returned_value(''.join([segment['_rendered_hl'] for segment in segments]) + self.hlstyle(), segments, output_raw) return construct_returned_value(''.join([
segment['_rendered_hl']
for segment in self._render_segments(theme, segments)
]) + self.hlstyle(), segments, output_raw)
divider_lengths = {
'left': {
'hard': self.strwidth(theme.get_divider('left', 'hard')),
'soft': self.strwidth(theme.get_divider('left', 'soft')),
},
'right': {
'hard': self.strwidth(theme.get_divider('right', 'hard')),
'soft': self.strwidth(theme.get_divider('right', 'soft')),
},
}
length = self._render_length(theme, segments, divider_lengths)
# 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'], reverse=True)
while sum([segment['_len'] for segment in segments]) > width and len(segments_priority): for segment in segments_priority:
segments.remove(segments_priority[0]) if self._render_length(theme, segments, divider_lengths) <= width:
segments_priority.pop(0) break
segments.remove(segment)
# Distribute the remaining space on spacer segments # 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['width'] == 'auto']
@ -256,6 +280,38 @@ class Renderer(object):
return construct_returned_value(rendered_highlighted, segments, output_raw) return construct_returned_value(rendered_highlighted, segments, output_raw)
def _render_length(self, theme, segments, divider_lengths):
'''Update segments lengths and return them
'''
segments_len = len(segments)
ret = 0
divider_spaces = theme.get_spaces()
for index, segment in enumerate(segments):
side = segment['side']
if segment['_contents_len'] is None:
segment_len = segment['_contents_len'] = self.strwidth(segment['contents'])
else:
segment_len = segment['_contents_len']
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 side == 'left' else prev_segment
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
outer_padding = int(bool(
(index == 0 and side == 'left') or
(index == segments_len - 1 and side == 'right')
))
draw_divider = segment['draw_' + divider_type + '_divider']
segment_len += segment['_space_left'] + segment['_space_right'] + outer_padding
if draw_divider:
segment_len += divider_lengths[side][divider_type] + divider_spaces
segment['_len'] = segment_len
ret += segment_len
return ret
def _render_segments(self, theme, segments, render_highlighted=True): def _render_segments(self, theme, segments, render_highlighted=True):
'''Internal segment rendering method. '''Internal segment rendering method.
@ -268,19 +324,19 @@ class Renderer(object):
statusline if render_highlighted is True. statusline if render_highlighted is True.
''' '''
segments_len = len(segments) segments_len = len(segments)
divider_spaces = theme.get_spaces()
for index, segment in enumerate(segments): for index, segment in enumerate(segments):
segment['_rendered_raw'] = '' side = segment['side']
segment['_rendered_hl'] = ''
prev_segment = segments[index - 1] if index > 0 else theme.EMPTY_SEGMENT 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 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 compare_segment = next_segment if side == 'left' else prev_segment
outer_padding = ' ' if (index == 0 and segment['side'] == 'left') or (index == segments_len - 1 and segment['side'] == 'right') else '' outer_padding = int(bool(
(index == 0 and side == 'left') or
(index == segments_len - 1 and side == 'right')
)) * ' '
divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
divider_raw = theme.get_divider(segment['side'], divider_type)
divider_spaces = theme.get_spaces()
divider_highlighted = '' divider_highlighted = ''
contents_raw = segment['contents'] contents_raw = segment['contents']
contents_highlighted = '' contents_highlighted = ''
@ -288,48 +344,64 @@ class Renderer(object):
# Pad segments first # Pad segments first
if draw_divider: if draw_divider:
if segment['side'] == 'left': divider_raw = theme.get_divider(side, divider_type).replace(' ', NBSP)
contents_raw = outer_padding + (segment['_space_left'] * ' ') + contents_raw + ((divider_spaces + segment['_space_right']) * ' ') if side == 'left':
contents_raw = (
outer_padding + (segment['_space_left'] * ' ')
+ contents_raw
+ ((divider_spaces + segment['_space_right']) * ' ')
)
else: else:
contents_raw = ((divider_spaces + segment['_space_left']) * ' ') + contents_raw + (segment['_space_right'] * ' ') + outer_padding contents_raw = (
((divider_spaces + segment['_space_left']) * ' ')
+ contents_raw
+ (segment['_space_right'] * ' ') + outer_padding
)
else: else:
if segment['side'] == 'left': if side == 'left':
contents_raw = outer_padding + (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') contents_raw = (
outer_padding + (segment['_space_left'] * ' ')
+ contents_raw
+ (segment['_space_right'] * ' ')
)
else: else:
contents_raw = (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') + outer_padding contents_raw = (
(segment['_space_left'] * ' ')
+ contents_raw
+ (segment['_space_right'] * ' ') + outer_padding
)
# Replace spaces with no-break spaces # Replace spaces with no-break spaces
divider_raw = divider_raw.replace(' ', NBSP)
contents_raw = contents_raw.translate(self.np_character_translations) contents_raw = contents_raw.translate(self.np_character_translations)
# Apply highlighting to padded dividers and contents # Apply highlighting to padded dividers and contents
if render_highlighted: if render_highlighted:
if divider_type == 'soft': if draw_divider:
divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight' if divider_type == 'soft':
divider_fg = segment[divider_highlight_group_key]['fg'] divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight'
divider_bg = segment[divider_highlight_group_key]['bg'] divider_fg = segment[divider_highlight_group_key]['fg']
else: divider_bg = segment[divider_highlight_group_key]['bg']
divider_fg = segment['highlight']['bg'] else:
divider_bg = compare_segment['highlight']['bg'] divider_fg = segment['highlight']['bg']
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) divider_bg = compare_segment['highlight']['bg']
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
# Append padded raw and highlighted segments to the rendered segment variables # Append padded raw and highlighted segments to the rendered segment variables
if draw_divider: if draw_divider:
if segment['side'] == 'left': if side == 'left':
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:
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 segment['side'] == 'left': if side == 'left':
segment['_rendered_raw'] += contents_raw segment['_rendered_raw'] = contents_raw
segment['_rendered_hl'] += contents_highlighted segment['_rendered_hl'] = contents_highlighted
else: else:
segment['_rendered_raw'] += contents_raw segment['_rendered_raw'] = contents_raw
segment['_rendered_hl'] += contents_highlighted segment['_rendered_hl'] = contents_highlighted
segment['_len'] = self.strwidth(segment['_rendered_raw'])
yield segment yield segment
@classmethod @classmethod