From 3f25952c13d5847d510c0cae22a8ba876638d570 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:40:16 +0200 Subject: Adding upstream version 2.8.3. Signed-off-by: Daniel Baumann --- powerline/renderer.py | 606 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 606 insertions(+) create mode 100644 powerline/renderer.py (limited to 'powerline/renderer.py') diff --git a/powerline/renderer.py b/powerline/renderer.py new file mode 100644 index 0000000..31aca80 --- /dev/null +++ b/powerline/renderer.py @@ -0,0 +1,606 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import os +import re +import operator + +from itertools import chain + +from powerline.theme import Theme +from powerline.lib.unicode import unichr, strwidth_ucs_2, strwidth_ucs_4 + + +NBSP = ' ' + + +np_control_character_translations = dict(( + # Control characters: ^@ … ^Y + (i1, '^' + unichr(i1 + 0x40)) for i1 in range(0x20) +)) +'''Control character translations + +Dictionary that maps characters in range 0x00–0x1F (inclusive) to strings +``'^@'``, ``'^A'`` and so on. + +.. note: maps tab to ``^I`` and newline to ``^J``. +''' + +np_invalid_character_translations = dict(( + # Invalid unicode characters obtained using 'surrogateescape' error + # handler. + (i2, '<{0:02x}>'.format(i2 - 0xDC00)) for i2 in range(0xDC80, 0xDD00) +)) +'''Invalid unicode character translations + +When using ``surrogateescape`` encoding error handling method characters in +range 0x80–0xFF (inclusive) are transformed into unpaired surrogate escape +unicode codepoints 0xDC80–0xDD00. This dictionary maps such characters to +``<80>``, ``<81>``, and so on: in Python-3 they cannot be printed or +converted to UTF-8 because UTF-8 standard does not allow surrogate escape +characters, not even paired ones. Python-2 contains a bug that allows such +action, but printing them in any case makes no sense. +''' + +# XXX: not using `r` because it makes no sense. +np_invalid_character_re = re.compile('(? 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) + + 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: + break + current_width = self._render_length(theme, segments, divider_widths) + if current_width > width: + # When there are not too much use more precise, but much slower + # width computation. It also finishes computations in case + # previous variant did not free enough space. + for segment in segments_priority: + segments.remove(segment) + current_width = self._render_length(theme, segments, divider_widths) + if current_width <= width: + break + del segments_priority + + # Distribute the remaining space on spacer segments + segments_spacers = [segment for segment in segments if segment['expand'] is not None] + if segments_spacers: + distribute_len, distribute_len_remainder = divmod(width - current_width, len(segments_spacers)) + for segment in segments_spacers: + 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 + elif output_width: + current_width = self._render_length(theme, segments, divider_widths) + + rendered_highlighted = self.hl_join([ + segment['_rendered_hl'] + for segment in self._render_segments(theme, segments, hl_args) + ]) + if rendered_highlighted: + rendered_highlighted += self.hlstyle(**hl_args) + + return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width) + + def _prepare_segments(self, segments, calculate_contents_len): + '''Translate non-printable characters and calculate segment width + ''' + for segment in segments: + segment['contents'] = translate_np(segment['contents']) + if calculate_contents_len: + for segment in segments: + if segment['literal_contents'][1]: + segment['_contents_len'] = segment['literal_contents'][0] + else: + segment['_contents_len'] = self.strwidth(segment['contents']) + + def _render_length(self, theme, segments, divider_widths): + '''Update segments lengths and return them + ''' + segments_len = len(segments) + ret = 0 + divider_spaces = theme.get_spaces() + prev_segment = theme.EMPTY_SEGMENT + try: + first_segment = next(iter(( + segment + for segment in segments + if not segment['literal_contents'][1] + ))) + except StopIteration: + first_segment = None + try: + last_segment = next(iter(( + segment + for segment in reversed(segments) + if not segment['literal_contents'][1] + ))) + except StopIteration: + last_segment = None + for index, segment in enumerate(segments): + side = segment['side'] + segment_len = segment['_contents_len'] + 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 + else: + compare_segment = prev_segment + + divider_type = 'soft' if 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 + )) * theme.outer_padding + + draw_divider = segment['draw_' + divider_type + '_divider'] + segment_len += outer_padding + if draw_divider: + segment_len += divider_widths[side][divider_type] + divider_spaces + prev_segment = segment + + segment['_len'] = segment_len + ret += segment_len + return ret + + def _render_segments(self, theme, segments, hl_args, render_highlighted=True): + '''Internal segment rendering method. + + This method loops through the segment array and compares the + foreground/background colors and divider properties and returns the + rendered statusline as a string. + + The method always renders the raw segment contents (i.e. without + highlighting strings added), and only renders the highlighted + statusline if render_highlighted is True. + ''' + segments_len = len(segments) + divider_spaces = theme.get_spaces() + prev_segment = theme.EMPTY_SEGMENT + try: + first_segment = next(iter(( + segment + for segment in segments + if not segment['literal_contents'][1] + ))) + except StopIteration: + first_segment = None + try: + last_segment = next(iter(( + segment + for segment in reversed(segments) + if not segment['literal_contents'][1] + ))) + except StopIteration: + last_segment = None + + 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 + else: + compare_segment = prev_segment + outer_padding = int(bool( + segment is first_segment + if side == 'left' else + segment is last_segment + )) * theme.outer_padding * ' ' + divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' + + divider_highlighted = '' + contents_raw = segment['contents'] + contents_highlighted = '' + draw_divider = segment['draw_' + divider_type + '_divider'] + + segment_hl_args = {} + segment_hl_args.update(segment['highlight']) + segment_hl_args.update(hl_args) + + # 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: + divider_raw = self.escape(theme.get_divider(side, divider_type)) + if side == 'left': + contents_raw = outer_padding + contents_raw + (divider_spaces * ' ') + else: + contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding + + if divider_type == 'soft': + divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight' + divider_fg = segment[divider_highlight_group_key]['fg'] + divider_bg = segment[divider_highlight_group_key]['bg'] + else: + divider_fg = segment['highlight']['bg'] + divider_bg = compare_segment['highlight']['bg'] + + if side == 'left': + if render_highlighted: + contents_highlighted = self.hl(self.escape(contents_raw), **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) + segment['_rendered_raw'] = divider_raw + contents_raw + segment['_rendered_hl'] = divider_highlighted + contents_highlighted + else: + if side == 'left': + contents_raw = outer_padding + contents_raw + else: + contents_raw = contents_raw + outer_padding + + contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args) + segment['_rendered_raw'] = contents_raw + segment['_rendered_hl'] = contents_highlighted + prev_segment = segment + else: + segment['_rendered_raw'] = ' ' * segment['literal_contents'][0] + segment['_rendered_hl'] = segment['literal_contents'][1] + yield segment + + def escape(self, string): + '''Method that escapes segment contents. + ''' + return string.translate(self.character_translations) + + def hlstyle(fg=None, bg=None, attrs=None, **kwargs): + '''Output highlight style string. + + Assuming highlighted string looks like ``{style}{contents}`` this method + should output ``{style}``. If it is called without arguments this method + is supposed to reset style to its default. + ''' + raise NotImplementedError + + def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs): + '''Output highlighted chunk. + + This implementation just outputs :py:meth:`hlstyle` joined with + ``contents``. + ''' + return self.hlstyle(fg, bg, attrs, **kwargs) + (contents or '') -- cgit v1.2.3