# Source code for powerline.renderer

    # vim:fileencoding=utf-8:noet
    from __future__ import (unicode_literals, division, absolute_import, print_function)

    import sys
    import os
    import re

    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('(?<![\uD800-\uDBFF])[\uDC80-\uDD00]')
    '''Regex that finds unpaired surrogate escape characters

    Search is only limited to the ones obtained from ``surrogateescape`` error
    handling method. This regex is only used for UCS-2 Python variants because
    in this case characters above 0xFFFF are represented as surrogate escapes
    characters and are thus subject to partial transformation if
    ``np_invalid_character_translations`` translation table is used.
    '''

    np_character_translations = np_control_character_translations.copy()
    '''Dictionary that contains non-printable character translations

    In UCS-4 versions of Python this is a union of
    ``np_invalid_character_translations`` and ``np_control_character_translations``
    dictionaries. In UCS-2 for technical reasons ``np_invalid_character_re`` is used
    instead and this dictionary only contains items from
    ``np_control_character_translations``.
    '''

    translate_np = (
    	(
    		lambda s: (
    			np_invalid_character_re.subn(
    				lambda match: (
    					np_invalid_character_translations[ord(match.group(0))]
    				), s
    			)[0].translate(np_character_translations)
    		)
    	) if sys.maxunicode < 0x10FFFF else (
    		lambda s: (
    			s.translate(np_character_translations)
    		)
    	)
    )
    '''Function that translates non-printable characters into printable strings

    Is used to translate control characters and surrogate escape characters
    obtained from ``surrogateescape`` encoding errors handling method into some
    printable sequences. See documentation for
    ``np_invalid_character_translations`` and
    ``np_control_character_translations`` for more details.
    '''

    def construct_returned_value(rendered_highlighted, segments, width, output_raw, output_width):
    	if not (output_raw or output_width):
    		return rendered_highlighted
    	else:
    		return (
    			(rendered_highlighted,)
    			+ ((''.join((segment['_rendered_raw'] for segment in segments)),) if output_raw else ())
    			+ ((width,) if output_width else ())
    		)

    class Renderer(object):
    	'''Object that is responsible for generating the highlighted string.

    	:param dict theme_config:
    		Main theme configuration.
    	:param local_themes:
    		Local themes. Is to be used by subclasses from ``.get_theme()`` method,
    		base class only records this parameter to a ``.local_themes`` attribute.
    	:param dict theme_kwargs:
    		Keyword arguments for ``Theme`` class constructor.
    	:param PowerlineLogger pl:
    		Object used for logging.
    	:param int ambiwidth:
    		Width of the characters with east asian width unicode attribute equal to
    		``A`` (Ambigious).
    	:param dict options:
    		Various options. Are normally not used by base renderer, but all options
    		are recorded as attributes.
    	'''

    	segment_info = {
    		'environ': os.environ,
    		'getcwd': getattr(os, 'getcwdu', os.getcwd),
    		'home': os.environ.get('HOME'),
    	}
    	'''Basic segment info

    	Is merged with local segment information by :py:meth:`get_segment_info`
    	method. Keys:

    	``environ``
    		Object containing environment variables. Must define at least the
    		following methods: ``.__getitem__(var)`` that raises ``KeyError`` in
    		case requested environment variable is not present, ``.get(var,
    		default=None)`` that works like ``dict.get`` and be able to be passed to
    		``Popen``.

    	``getcwd``
    		Function that returns current working directory. Will be called without
    		any arguments, should return ``unicode`` or (in python-2) regular
    		string.

    	``home``
    		String containing path to home directory. Should be ``unicode`` or (in
    		python-2) regular string or ``None``.
    	'''

    	character_translations = {}
    	'''Character translations for use in escape() function.

    	See documentation of ``unicode.translate`` for details.
    	'''

    	def __init__(self,
    	             theme_config,
    	             local_themes,
    	             theme_kwargs,
    	             pl,
    	             ambiwidth=1,
    	             **options):
    		self.__dict__.update(options)
    		self.theme_config = theme_config
    		theme_kwargs['pl'] = pl
    		self.pl = pl
    		if theme_config.get('use_non_breaking_spaces', True):
    			self.character_translations = self.character_translations.copy()
    			self.character_translations[ord(' ')] = NBSP
    		self.theme = Theme(theme_config=theme_config, **theme_kwargs)
    		self.local_themes = local_themes
    		self.theme_kwargs = theme_kwargs
    		self.width_data = {
    			'N': 1,          # Neutral
    			'Na': 1,         # Narrow
    			'A': ambiwidth,  # Ambigious
    			'H': 1,          # Half-width
    			'W': 2,          # Wide
    			'F': 2,          # Fullwidth
    		}

    	strwidth = lambda self, s: (
    		(strwidth_ucs_2 if sys.maxunicode < 0x10FFFF else strwidth_ucs_4)(
    			self.width_data, s)
    	)
    	'''Function that returns string width.

    	Is used to calculate the place given string occupies when handling
    	``width`` argument to ``.render()`` method. Must take east asian width
    	into account.

    	:param unicode string:
    		String whose width will be calculated.

    	:return: unsigned integer.
    	'''

    	def get_theme(self, matcher_info):
    		'''Get Theme object.

    		Is to be overridden by subclasses to support local themes, this variant
    		only returns ``.theme`` attribute.

    		:param matcher_info:
    			Parameter ``matcher_info`` that ``.render()`` method received.
    			Unused.
    		'''
    		return self.theme

    	def shutdown(self):
    		'''Prepare for interpreter shutdown. The only job it is supposed to do
    		is calling ``.shutdown()`` method for all theme objects. Should be
    		overridden by subclasses in case they support local themes.
    		'''
    		self.theme.shutdown()

    	def get_segment_info(self, segment_info, mode):
    		'''Get segment information.

    		Must return a dictionary containing at least ``home``, ``environ`` and
    		``getcwd`` keys (see documentation for ``segment_info`` attribute). This
    		implementation merges ``segment_info`` dictionary passed to
    		``.render()`` method with ``.segment_info`` attribute, preferring keys
    		from the former. It also replaces ``getcwd`` key with function returning
    		``segment_info['environ']['PWD']`` in case ``PWD`` variable is
    		available.

    		:param dict segment_info:
    			Segment information that was passed to ``.render()`` method.

    		:return: dict with segment information.
    		'''
    		r = self.segment_info.copy()
    		r['mode'] = mode
    		if segment_info:
    			r.update(segment_info)
    		if 'PWD' in r['environ']:
    			r['getcwd'] = lambda: r['environ']['PWD']
    		return r

    	def render_above_lines(self, **kwargs):
    		'''Render all segments in the {theme}/segments/above list

    		Rendering happens in the reversed order. Parameters are the same as in
    		.render() method.

    		:yield: rendered line.
    		'''

    		theme = self.get_theme(kwargs.get('matcher_info', None))
    		for line in range(theme.get_line_number() - 1, 0, -1):
    			yield self.render(side=None, line=line, **kwargs)

    	def render(self, mode=None, width=None, side=None, line=0, output_raw=False, output_width=False, segment_info=None, matcher_info=None):
    		'''Render all segments.

    		When a width is provided, low-priority segments are dropped one at
    		a time until the line is shorter than the width, or only segments
    		with a negative priority are left. If one or more segments with
    		``"width": "auto"`` are provided they will fill the remaining space
    		until the desired width is reached.

    		:param str mode:
    			Mode string. Affects contents (colors and the set of segments) of
    			rendered string.
    		:param int width:
    			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.
    			If not present all sides are rendered.
    		:param int line:
    			Line number for which segments should be obtained. Is counted from
    			zero (botmost line).
    		:param bool output_raw:
    			Changes the output: if this parameter is ``True`` then in place of
    			one string this method outputs a pair ``(colored_string,
    			colorless_string)``.
    		:param bool output_width:
    			Changes the output: if this parameter is ``True`` then in place of
    			one string this method outputs a pair ``(colored_string,
    			string_width)``. Returns a three-tuple if ``output_raw`` is also
    			``True``: ``(colored_string, colorless_string, string_width)``.
    		:param dict segment_info:
    			Segment information. See also :py:meth:`get_segment_info` method.
    		:param matcher_info:
    			Matcher information. Is processed in :py:meth:`get_segment_info`
    			method.
    		'''
    		theme = self.get_theme(matcher_info)
    		return self.do_render(
    			mode=mode,
    			width=width,
    			side=side,
    			line=line,
    			output_raw=output_raw,
    			output_width=output_width,
    			segment_info=self.get_segment_info(segment_info, mode),
    			theme=theme,
    		)

    	def compute_divider_widths(self, theme):
    		return {
    			'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')),
    			},
    		}

    	def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme):
    		'''Like Renderer.render(), but accept theme in place of matcher_info
    		'''
    		segments = list(theme.get_segments(side, line, segment_info, mode))

    		current_width = 0

    		self._prepare_segments(segments, output_width or width)

    		if not width:
    			# No width specified, so we don’t need to crop or pad anything
    			if output_width:
    				current_width = self._render_length(theme, segments, self.compute_divider_widths(theme))
    			return construct_returned_value(''.join([
    				segment['_rendered_hl']
    				for segment in self._render_segments(theme, segments)
    			]) + self.hlstyle(), segments, current_width, output_raw, output_width)

    		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)
    		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)

    			segments_priority = iter(segments_priority)
    			if current_width > width and len(segments) > 100:
    				# When there are too many segments use faster, but less correct
    				# algorythm 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 = ''.join([segment['_rendered_hl'] for segment in self._render_segments(theme, segments)])
    		if rendered_highlighted:
    			rendered_highlighted += self.hlstyle()

    		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
    				))

    				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, 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
    				)) * ' '
    				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']

    				# 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['highlight'])
    							divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
    						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)
    							contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
    						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['highlight'])
    					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):
    		'''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):
    		'''Output highlighted chunk.

    		This implementation just outputs :py:meth:`hlstyle` joined with
    		``contents``.
    		'''
    		return self.hlstyle(fg, bg, attrs) + (contents or '')