Make the renderer work with new theme/colorscheme API

This commit is contained in:
Kim Silkebækken 2012-12-10 17:47:07 +01:00
parent d7ff3f72a6
commit 011bacb8c3
7 changed files with 160 additions and 123 deletions

View File

@ -49,6 +49,23 @@ class Colorscheme(object):
'attr': group_attr_flag,
}
def get_group_highlighting(self, group):
'''Return highlighting information for all modes of a highlighting group.
'''
group_highlighting = {}
for mode, mode_group in self.modes_groups.items():
try:
group_highlighting[mode] = mode_group[group]
except TypeError:
for try_group in group:
if try_group in self.modes_groups[mode]:
group_highlighting[mode] = mode_group[try_group]
break
finally:
if mode not in group_highlighting:
raise KeyError('Highlighting groups not found in colorscheme: {0}'.format(group))
return group_highlighting
def get_highlighting(self, group, mode=None):
'''Return highlighting information for a highlighting group and mode.
@ -58,6 +75,14 @@ class Colorscheme(object):
if not mode or mode not in self.modes_groups:
mode = self.default_mode_key
try:
return self.modes_groups[mode][group]
except TypeError:
for try_group in group:
if try_group in self.modes_groups[mode]:
return self.modes_groups[mode][try_group]
raise KeyError('Highlighting groups not found in colorscheme: {0}'.format(group))
return self.modes_groups[mode][group]
def _get_attr_flag(self, attributes):

View File

@ -29,17 +29,17 @@ class Powerline(object):
# Load and initialize colorscheme
colorscheme_config = self._load_json_config(os.path.join('colorschemes', self.config_ext['colorscheme']))
self.colorscheme = Colorscheme(colorscheme_config)
colorscheme = Colorscheme(colorscheme_config)
# Load and initialize extension theme
theme_config = self._load_json_config(os.path.join('themes', ext, self.config_ext['theme']))
self.theme = Theme(ext, theme_config, self.config)
self.theme = Theme(ext, colorscheme, theme_config, self.config)
# Load and initialize extension renderer
renderer_module_name = 'powerline.ext.{0}.renderer'.format(ext)
renderer_class_name = '{0}Renderer'.format(ext.capitalize())
renderer_class = getattr(importlib.import_module(renderer_module_name), renderer_class_name)
self.renderer = renderer_class(self.colorscheme, self.theme)
self.renderer = renderer_class(self.theme)
def _load_json_config(self, config_file):
config_file += '.json'
@ -50,3 +50,6 @@ class Powerline(object):
return json.load(config_file_fp)
raise IOError('Config file not found in search path: {0}'.format(config_file))
def render(self, mode, width=None):
return self.renderer.render(mode, width)

View File

@ -6,8 +6,8 @@ from powerline.renderer import Renderer
class VimRenderer(Renderer):
'''Powerline vim segment renderer.
'''
def __init__(self, colorscheme, theme):
super(VimRenderer, self).__init__(colorscheme, theme)
def __init__(self, theme):
super(VimRenderer, self).__init__(theme)
self.hl_groups = {}
def hl(self, fg=None, bg=None, attr=None):
@ -41,11 +41,11 @@ class VimRenderer(Renderer):
if attr:
hl_group['attr'] = []
if attr & Renderer.ATTR_BOLD:
if attr & self.ATTR_BOLD:
hl_group['attr'].append('bold')
if attr & Renderer.ATTR_ITALIC:
if attr & self.ATTR_ITALIC:
hl_group['attr'].append('italic')
if attr & Renderer.ATTR_UNDERLINE:
if attr & self.ATTR_UNDERLINE:
hl_group['attr'].append('underline')
hl_group['name'] = 'Pl_' + \

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
import os
import vim
from bindings import vim_get_func
from powerline.ext.vim.bindings import vim_get_func
vim_funcs = {
'col': vim_get_func('col', rettype=int),
@ -48,12 +49,12 @@ def mode(override=None):
mode = vim_funcs['mode']()
if not override:
return (mode, vim_modes[mode])
return vim_modes[mode]
try:
return (mode, override[mode])
return override[mode]
except IndexError:
return (mode, vim_modes[mode])
return vim_modes[mode]
def modified_indicator(text=u'+'):
@ -93,7 +94,7 @@ def branch():
def file_directory():
'''Return file directory (head component of the file path).
'''
return vim_funcs['expand']('%:~:.:h')
return vim_funcs['expand']('%:~:.:h') + os.sep
def file_name():
@ -126,13 +127,20 @@ def file_type():
return vim.eval('&filetype') or None
def line_percent():
def line_percent(gradient=False):
'''Return the cursor position in the file as a percentage.
'''
line_current = vim_funcs['line']('.')
line_last = vim_funcs['line']('$')
percentage = int(line_current * 100 // line_last)
return line_current * 100 // line_last
if not gradient:
return percentage
return {
'contents': percentage,
'highlight': 'line_percent_gradient' + str(int(5 * percentage // 100) + 1),
}
def line_current():

View File

@ -1,23 +1,17 @@
# -*- coding: utf-8 -*-
from segment import mksegment
class Renderer(object):
ATTR_BOLD = 1
ATTR_ITALIC = 2
ATTR_UNDERLINE = 4
def __init__(self, colorscheme, theme):
pass
def __init__(self, theme):
self.segments = []
self.theme = theme
def render(self, mode, 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.
'''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
@ -25,71 +19,8 @@ class Renderer(object):
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)
self.segments = self.theme.get_segments()
rendered_highlighted = self._render_segments(mode)
if not width:
# No width specified, so we don't need to crop or pad anything
@ -103,10 +34,10 @@ class Renderer(object):
segments_priority.pop(0)
# Do another render pass so we can calculate the correct amount of filler space
render_segments(self.segments)
self._render_segments(mode, False)
# Distribute the remaining space on the filler segments
segments_fillers = [segment for segment in self.segments if segment['filler'] is True]
segments_fillers = [segment for segment in self.segments if segment['type'] == 'filler']
if segments_fillers:
segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len()), len(segments_fillers))
segments_fillers_contents = ' ' * segments_fillers_len
@ -115,7 +46,65 @@ class Renderer(object):
# Add remainder whitespace to the first filler segment
segments_fillers[0]['contents'] += ' ' * segments_fillers_remainder
return render_segments(self.segments)
return self._render_segments(mode)
def _render_segments(self, mode, 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.
'''
rendered_highlighted = u''
segments_len = len(self.segments)
for index, segment in enumerate(self.segments):
prev_segment = self.segments[index - 1] if index > 0 else None
next_segment = self.segments[index + 1] if index < segments_len - 1 else None
compare_segment = next_segment if segment['side'] == 'left' else prev_segment
segment['rendered_raw'] = u''
outer_padding = ' ' if index == 0 or index == segments_len - 1 else ''
divider_type = 'soft' if compare_segment['highlight'][mode]['bg'] == segment['highlight'][mode]['bg'] else 'hard'
divider = self.theme.get_divider(segment['side'], divider_type)
divider_hl = ''
segment_hl = ''
if render_highlighted:
if divider_type == 'hard':
divider_hl = self.hl(segment['highlight'][mode]['bg'], compare_segment['highlight'][mode]['bg'], False)
segment_hl = self.hl(**segment['highlight'][mode])
if segment['type'] == 'filler':
rendered_highlighted += segment['contents'] or ''
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'] == 'left':
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'] == 'left':
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:
raise ValueError('Unknown segment type')
return rendered_highlighted
def _total_len(self):
'''Return total/rendered length of all segments.

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from colorscheme import cterm_to_hex
def mksegment(contents=None, cterm_fg=False, cterm_bg=False, attr=False, hex_fg=False, hex_bg=False, side='l', draw_divider=True, priority=-1, filler=False):
'''Convenience wrapper for segment generation.
'''
if contents is not None or filler:
try:
contents = unicode(contents or u'')
except UnicodeDecodeError:
contents = contents.decode('utf-8') or u''
return {
'contents': contents,
'fg': (cterm_fg, hex_fg or cterm_to_hex.get(cterm_fg, 0xffffff)),
'bg': (cterm_bg, hex_bg or cterm_to_hex.get(cterm_bg, 0x000000)),
'attr': attr,
'side': side,
'draw_divider': False if filler else draw_divider,
'priority': priority,
'filler': filler,
}

View File

@ -4,20 +4,22 @@ import importlib
class Theme(object):
def __init__(self, ext, theme_config, common_config):
def __init__(self, ext, colorscheme, theme_config, common_config):
self.colorscheme = colorscheme
self.dividers = theme_config.get('dividers', common_config['dividers'])
self.segments = []
for side in ['left', 'right']:
for segment in theme_config['segments'].get(side, []):
contents = None
contents_func = None
segment_type = segment.get('type', 'function')
if segment_type == 'function':
# Import segment function and assign it to the contents
function_module = 'powerline.ext.{0}.segments'.format(ext)
function_name = segment['name']
contents = getattr(importlib.import_module(function_module), function_name)
contents_func = getattr(importlib.import_module(function_module), function_name)
elif segment_type == 'string':
contents = segment.get('contents')
elif segment_type == 'filler':
@ -25,11 +27,14 @@ class Theme(object):
else:
raise TypeError('Unknown segment type: {0}'.format(segment_type))
highlighting_group = segment.get('highlight', segment.get('name'))
self.segments.append({
'type': segment_type,
'highlight': segment.get('highlight', segment.get('name')),
'before': segment.get('before'),
'after': segment.get('after'),
'highlight': self.colorscheme.get_group_highlighting(highlighting_group),
'before': segment.get('before', ''),
'after': segment.get('after', ''),
'contents_func': contents_func,
'contents': contents,
'args': segment.get('args', {}),
'ljust': segment.get('ljust', False),
@ -42,7 +47,38 @@ class Theme(object):
})
def get_divider(self, side='left', type='soft'):
'''Return segment divider.
'''
return self.dividers[side][type]
def get_segments(self):
return self.segments
'''Return all segments.
Function segments are called, and all segments get their before/after
and ljust/rjust properties applied.
'''
return_segments = []
for segment in self.segments:
if segment['type'] == 'function':
contents_func_ret = segment['contents_func'](**segment['args'])
if contents_func_ret is None:
continue
try:
segment['highlight'] = self.colorscheme.get_group_highlighting(contents_func_ret['highlight'])
segment['contents'] = contents_func_ret['contents']
except TypeError:
segment['contents'] = contents_func_ret
elif segment['type'] == 'filler' or (segment['type'] == 'string' and segment['contents'] is not None):
pass
else:
continue
segment['contents'] = unicode(segment['before'] + unicode(segment['contents']) + segment['after'])\
.ljust(segment['ljust'])\
.rjust(segment['rjust'])
return_segments.append(segment)
return return_segments