diff --git a/powerline/core.py b/powerline/core.py index 6159f473..04946d39 100644 --- a/powerline/core.py +++ b/powerline/core.py @@ -1,24 +1,7 @@ # -*- coding: utf-8 -*- -from segment import mksegment - class Powerline(object): - ATTR_BOLD = 1 - ATTR_ITALIC = 2 - ATTR_UNDERLINE = 4 - - dividers = { - 'l': { - 'hard': u'⮀', - 'soft': u'⮁', - }, - 'r': { - 'hard': u'⮂', - 'soft': u'⮃', - }, - } - def __init__(self, segments): '''Create a new Powerline. @@ -26,117 +9,8 @@ class Powerline(object): dropped from the segment array. ''' self.segments = [segment for segment in segments if segment['contents'] or segment['filler']] - self._hl = {} def render(self, renderer, width=None): - '''Render all the segments with the specified renderer. + r = renderer(self.segments) - This method loops through the segment array and compares the - foreground/background colors and divider properties and returns the - rendered statusline as a string. - - 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 filler segments are - provided they will fill the remaining space until the desired width is - reached. - ''' - def render_segments(segments, render_highlighted=True): - '''Render a segment array. - - By default this function renders both raw (un-highlighted segments - used for calculating final width) and highlighted segments. The raw - rendering is used for calculating the total width for dropping - low-priority segments. - ''' - rendered_highlighted = u'' - segments_len = len(segments) - empty_segment = mksegment() - - for idx, segment in enumerate(segments): - prev = segments[idx - 1] if idx > 0 else empty_segment - next = segments[idx + 1] if idx < segments_len - 1 else empty_segment - - segment['rendered_raw'] = u'' - compare = next if segment['side'] == 'l' else prev - outer_padding = ' ' if idx == 0 or idx == segments_len - 1 else '' - divider_type = 'soft' if compare['bg'] == segment['bg'] else 'hard' - divider = self.dividers[segment['side']][divider_type] - divider_hl = '' - segment_hl = '' - - if render_highlighted: - # Generate and cache renderer highlighting - if divider_type == 'hard': - hl_key = (segment['bg'], compare['bg']) - if not hl_key in self._hl: - self._hl[hl_key] = renderer.hl(*hl_key) - divider_hl = self._hl[hl_key] - - hl_key = (segment['fg'], segment['bg'], segment['attr']) - if not hl_key in self._hl: - self._hl[hl_key] = renderer.hl(*hl_key) - segment_hl = self._hl[hl_key] - - if segment['filler']: - # Filler segments shouldn't be padded - rendered_highlighted += segment['contents'] - 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['side'] == 'l': - segment['rendered_raw'] += outer_padding + segment['contents'] + ' ' + divider + ' ' - rendered_highlighted += segment_hl + outer_padding + segment['contents'] + ' ' + divider_hl + divider + ' ' - else: - segment['rendered_raw'] += ' ' + divider + ' ' + segment['contents'] + outer_padding - rendered_highlighted += ' ' + divider_hl + divider + segment_hl + ' ' + segment['contents'] + outer_padding - elif segment['contents']: - # Segments without divider - if segment['side'] == 'l': - segment['rendered_raw'] += outer_padding + segment['contents'] - rendered_highlighted += segment_hl + outer_padding + segment['contents'] - else: - segment['rendered_raw'] += segment['contents'] + outer_padding - rendered_highlighted += segment_hl + segment['contents'] + outer_padding - else: - # Unknown segment type, skip it - continue - - return rendered_highlighted - - rendered_highlighted = render_segments(self.segments) - - if not width: - # No width specified, so we don't need to crop or pad anything - return rendered_highlighted - - # Create an ordered list of segments that can be dropped - segments_priority = [segment for segment in sorted(self.segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0] - - while self._total_len() > width and len(segments_priority): - self.segments.remove(segments_priority[0]) - segments_priority.pop(0) - - # Do another render pass so we can calculate the correct amount of filler space - render_segments(self.segments) - - # Distribute the remaining space on the filler segments - segments_fillers = [segment for segment in self.segments if segment['filler'] is True] - if segments_fillers: - segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len()), len(segments_fillers)) - segments_fillers_contents = ' ' * 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'] += ' ' * segments_fillers_remainder - - return render_segments(self.segments) - - def _total_len(self): - '''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 self.segments])) + return r.render(width) diff --git a/powerline/ext/terminal/__init__.py b/powerline/ext/terminal/__init__.py index e69de29b..5bf82fe6 100644 --- a/powerline/ext/terminal/__init__.py +++ b/powerline/ext/terminal/__init__.py @@ -0,0 +1 @@ +from renderer import TerminalRenderer # NOQA diff --git a/powerline/ext/terminal/renderer.py b/powerline/ext/terminal/renderer.py index ff55dff2..0c5c5ebe 100644 --- a/powerline/ext/terminal/renderer.py +++ b/powerline/ext/terminal/renderer.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python +# -*- coding: utf-8 -*- -from lib.core import Powerline -from lib.renderers import SegmentRenderer +from powerline.renderer import Renderer -class TerminalSegmentRenderer(SegmentRenderer): +class TerminalRenderer(Renderer): '''Powerline terminal segment renderer. ''' def hl(self, fg=None, bg=None, attr=None): @@ -32,7 +31,7 @@ class TerminalSegmentRenderer(SegmentRenderer): if attr is False: ansi += [22] else: - if attr & Powerline.ATTR_BOLD: + if attr & Renderer.ATTR_BOLD: ansi += [1] return '[{0}m'.format(';'.join(str(attr) for attr in ansi)) diff --git a/powerline/ext/tmux/__init__.py b/powerline/ext/tmux/__init__.py index e69de29b..c8e23c53 100644 --- a/powerline/ext/tmux/__init__.py +++ b/powerline/ext/tmux/__init__.py @@ -0,0 +1 @@ +from renderer import TmuxRenderer # NOQA diff --git a/powerline/ext/tmux/renderer.py b/powerline/ext/tmux/renderer.py index 07db9ff1..458cb191 100644 --- a/powerline/ext/tmux/renderer.py +++ b/powerline/ext/tmux/renderer.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python +# -*- coding: utf-8 -*- -from lib.core import Powerline -from lib.renderers import SegmentRenderer +from powerline.renderer import Renderer -class TmuxSegmentRenderer(SegmentRenderer): +class TmuxRenderer(Renderer): '''Powerline tmux segment renderer. ''' def hl(self, fg=None, bg=None, attr=None): @@ -22,15 +21,15 @@ class TmuxSegmentRenderer(SegmentRenderer): if attr is False: tmux_attr += ['nobold', 'noitalics', 'nounderscore'] else: - if attr & Powerline.ATTR_BOLD: + if attr & Renderer.ATTR_BOLD: tmux_attr += ['bold'] else: tmux_attr += ['nobold'] - if attr & Powerline.ATTR_ITALIC: + if attr & Renderer.ATTR_ITALIC: tmux_attr += ['italics'] else: tmux_attr += ['noitalics'] - if attr & Powerline.ATTR_UNDERLINE: + if attr & Renderer.ATTR_UNDERLINE: tmux_attr += ['underscore'] else: tmux_attr += ['nounderscore'] diff --git a/powerline/ext/vim/__init__.py b/powerline/ext/vim/__init__.py index e69de29b..69d94051 100644 --- a/powerline/ext/vim/__init__.py +++ b/powerline/ext/vim/__init__.py @@ -0,0 +1 @@ +from renderer import VimRenderer # NOQA diff --git a/powerline/ext/vim/renderer.py b/powerline/ext/vim/renderer.py index 07a4dbb2..dc64f2d1 100644 --- a/powerline/ext/vim/renderer.py +++ b/powerline/ext/vim/renderer.py @@ -1,13 +1,13 @@ -#!/usr/bin/env python +# -*- coding: utf-8 -*- -from lib.core import Powerline -from lib.renderers import SegmentRenderer +from powerline.renderer import Renderer -class VimSegmentRenderer(SegmentRenderer): +class VimRenderer(Renderer): '''Powerline vim segment renderer. ''' - def __init__(self): + def __init__(self, segments): + super(VimRenderer, self).__init__(segments) self.hl_groups = {} def hl(self, fg=None, bg=None, attr=None): @@ -41,11 +41,11 @@ class VimSegmentRenderer(SegmentRenderer): if attr: hl_group['attr'] = [] - if attr & Powerline.ATTR_BOLD: + if attr & Renderer.ATTR_BOLD: hl_group['attr'].append('bold') - if attr & Powerline.ATTR_ITALIC: + if attr & Renderer.ATTR_ITALIC: hl_group['attr'].append('italic') - if attr & Powerline.ATTR_UNDERLINE: + if attr & Renderer.ATTR_UNDERLINE: hl_group['attr'].append('underline') hl_group['name'] = 'Pl_' + \ diff --git a/powerline/renderer.py b/powerline/renderer.py index 40a96afc..eb06e135 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -1 +1,140 @@ # -*- coding: utf-8 -*- + +from segment import mksegment + + +class Renderer(object): + ATTR_BOLD = 1 + ATTR_ITALIC = 2 + ATTR_UNDERLINE = 4 + + dividers = { + 'l': { + 'hard': u'⮀', + 'soft': u'⮁', + }, + 'r': { + 'hard': u'⮂', + 'soft': u'⮃', + }, + } + + def __init__(self, segments): + self.segments = segments + self._hl = {} + + def render(self, width=None): + '''Render all the segments with the specified renderer. + + This method loops through the segment array and compares the + foreground/background colors and divider properties and returns the + rendered statusline as a string. + + 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 filler segments are + provided they will fill the remaining space until the desired width is + reached. + ''' + def render_segments(segments, render_highlighted=True): + '''Render a segment array. + + By default this function renders both raw (un-highlighted segments + used for calculating final width) and highlighted segments. The raw + rendering is used for calculating the total width for dropping + low-priority segments. + ''' + rendered_highlighted = u'' + segments_len = len(segments) + empty_segment = mksegment() + + for idx, segment in enumerate(segments): + prev = segments[idx - 1] if idx > 0 else empty_segment + next = segments[idx + 1] if idx < segments_len - 1 else empty_segment + + segment['rendered_raw'] = u'' + compare = next if segment['side'] == 'l' else prev + outer_padding = ' ' if idx == 0 or idx == segments_len - 1 else '' + divider_type = 'soft' if compare['bg'] == segment['bg'] else 'hard' + divider = self.dividers[segment['side']][divider_type] + divider_hl = '' + segment_hl = '' + + if render_highlighted: + # Generate and cache renderer highlighting + if divider_type == 'hard': + hl_key = (segment['bg'], compare['bg']) + if not hl_key in self._hl: + self._hl[hl_key] = self.hl(*hl_key) + divider_hl = self._hl[hl_key] + + hl_key = (segment['fg'], segment['bg'], segment['attr']) + if not hl_key in self._hl: + self._hl[hl_key] = self.hl(*hl_key) + segment_hl = self._hl[hl_key] + + if segment['filler']: + # Filler segments shouldn't be padded + rendered_highlighted += segment['contents'] + 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['side'] == 'l': + segment['rendered_raw'] += outer_padding + segment['contents'] + ' ' + divider + ' ' + rendered_highlighted += segment_hl + outer_padding + segment['contents'] + ' ' + divider_hl + divider + ' ' + else: + segment['rendered_raw'] += ' ' + divider + ' ' + segment['contents'] + outer_padding + rendered_highlighted += ' ' + divider_hl + divider + segment_hl + ' ' + segment['contents'] + outer_padding + elif segment['contents']: + # Segments without divider + if segment['side'] == 'l': + segment['rendered_raw'] += outer_padding + segment['contents'] + rendered_highlighted += segment_hl + outer_padding + segment['contents'] + else: + segment['rendered_raw'] += segment['contents'] + outer_padding + rendered_highlighted += segment_hl + segment['contents'] + outer_padding + else: + # Unknown segment type, skip it + continue + + return rendered_highlighted + + rendered_highlighted = render_segments(self.segments) + + if not width: + # No width specified, so we don't need to crop or pad anything + return rendered_highlighted + + # Create an ordered list of segments that can be dropped + segments_priority = [segment for segment in sorted(self.segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0] + + while self._total_len() > width and len(segments_priority): + self.segments.remove(segments_priority[0]) + segments_priority.pop(0) + + # Do another render pass so we can calculate the correct amount of filler space + render_segments(self.segments) + + # Distribute the remaining space on the filler segments + segments_fillers = [segment for segment in self.segments if segment['filler'] is True] + if segments_fillers: + segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len()), len(segments_fillers)) + segments_fillers_contents = ' ' * 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'] += ' ' * segments_fillers_remainder + + return render_segments(self.segments) + + def _total_len(self): + '''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 self.segments])) + + def hl(self, fg=None, bg=None, attr=None): + raise NotImplementedError