Added local theme support (mainly for buffer-local themes)

This commit is contained in:
ZyX 2012-12-14 18:18:42 +04:00 committed by Kim Silkebækken
parent f550bd37f8
commit da6367a897
9 changed files with 134 additions and 37 deletions

View File

@ -22,7 +22,10 @@
}, },
"vim": { "vim": {
"colorscheme": "default", "colorscheme": "default",
"theme": "default" "theme": "default",
"local_themes": {
"help": "help"
}
} }
} }
} }

View File

@ -6,7 +6,8 @@ import os
import sys import sys
from colorscheme import Colorscheme from colorscheme import Colorscheme
from theme import Theme from segments import Segments
from matchers import Matchers
class Powerline(object): class Powerline(object):
@ -29,14 +30,33 @@ class Powerline(object):
colorscheme = Colorscheme(colorscheme_config) colorscheme = Colorscheme(colorscheme_config)
# Load and initialize extension theme # Load and initialize extension theme
theme_config = self._load_json_config(os.path.join('themes', ext, self.config_ext['theme'])) theme_config = self._load_theme_config(ext, self.config_ext.get('theme', 'default'))
self.theme = Theme(ext, colorscheme, theme_config, self.config)
path = [os.path.expanduser(path) for path in self.config.get('paths', [])]
get_segment = Segments(ext, path, colorscheme).get
get_matcher = Matchers(ext, path).get
theme_kwargs = {
'ext': ext,
'colorscheme': colorscheme,
'common_config': self.config,
'get_segment': get_segment
}
local_themes = {}
for key, local_theme_name in self.config_ext.get('local_themes', {}).iteritems():
key = get_matcher(key)
local_themes[key] = {'config': self._load_theme_config(ext, local_theme_name)}
# Load and initialize extension renderer # Load and initialize extension renderer
renderer_module_name = 'powerline.ext.{0}.renderer'.format(ext) renderer_module_name = 'powerline.ext.{0}.renderer'.format(ext)
renderer_class_name = '{0}Renderer'.format(ext.capitalize()) renderer_class_name = '{0}Renderer'.format(ext.capitalize())
renderer_class = getattr(importlib.import_module(renderer_module_name), renderer_class_name) Renderer = getattr(importlib.import_module(renderer_module_name), renderer_class_name)
self.renderer = renderer_class(self.theme) self.renderer = Renderer(theme_config, local_themes, theme_kwargs)
def _load_theme_config(self, ext, name):
return self._load_json_config(os.path.join('themes', ext, name))
def _load_json_config(self, config_file): def _load_json_config(self, config_file):
config_file += '.json' config_file += '.json'

View File

@ -0,0 +1,5 @@
import vim
def help():
return bool(int(vim.eval('&buftype is# "help"')))

View File

@ -16,8 +16,8 @@ class VimRenderer(Renderer):
''' '''
PERCENT_PLACEHOLDER = u'' PERCENT_PLACEHOLDER = u''
def __init__(self, theme): def __init__(self, *args, **kwargs):
super(VimRenderer, self).__init__(theme) super(VimRenderer, self).__init__(*args, **kwargs)
self.hl_groups = {} self.hl_groups = {}
def render(self, winnr): def render(self, winnr):
@ -40,7 +40,7 @@ class VimRenderer(Renderer):
contents_cached = vim_getwinvar(winnr, 'powerline') contents_cached = vim_getwinvar(winnr, 'powerline')
contents_override = {k: contents_cached[k].decode('utf-8') for k in contents_cached.keys()} contents_override = {k: contents_cached[k].decode('utf-8') for k in contents_cached.keys()}
statusline = super(VimRenderer, self).render(mode, winwidth, contents_override) statusline = super(VimRenderer, self).render(mode, width=winwidth, contents_override=contents_override)
statusline = statusline.replace(self.PERCENT_PLACEHOLDER, '%%') statusline = statusline.replace(self.PERCENT_PLACEHOLDER, '%%')
return statusline return statusline

24
powerline/matchers.py Normal file
View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from importlib import import_module
import sys
class Matchers(object):
def __init__(self, ext, path):
self.ext = ext
self.path = path
def get(self, match_name):
match_module, separator, match_function = match_name.rpartition('.')
if not separator:
match_module = 'powerline.ext.{0}.matchers'.format(self.ext)
match_function = match_name
oldpath = sys.path
sys.path = self.path + sys.path
try:
return getattr(import_module(match_module), match_function)
finally:
sys.path = oldpath

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from colorscheme import Colorscheme from colorscheme import Colorscheme
from theme import Theme
class Renderer(object): class Renderer(object):
@ -8,9 +9,21 @@ class Renderer(object):
ATTR_ITALIC = 2 ATTR_ITALIC = 2
ATTR_UNDERLINE = 4 ATTR_UNDERLINE = 4
def __init__(self, theme): def __init__(self, theme_config, local_themes, theme_kwargs):
self.theme = Theme(theme_config=theme_config, **theme_kwargs)
self.local_themes = local_themes
self.theme_kwargs = theme_kwargs
self.segments = [] self.segments = []
self.theme = theme
def get_theme(self):
for matcher in self.local_themes.iterkeys():
if matcher():
match = self.local_themes[matcher]
if 'config' in match:
match['theme'] = Theme(theme_config=match.pop('config'), **self.theme_kwargs)
return match['theme']
else:
return self.theme
def render(self, mode, width=None, contents_override=None): def render(self, mode, width=None, contents_override=None):
'''Render all segments. '''Render all segments.
@ -21,36 +34,40 @@ class Renderer(object):
provided they will fill the remaining space until the desired width is provided they will fill the remaining space until the desired width is
reached. reached.
''' '''
self.segments = self.theme.get_segments(mode, contents_override)
rendered_highlighted = self._render_segments(mode) theme = self.get_theme()
segments = theme.get_segments(mode, contents_override)
self.segments = segments
rendered_highlighted = self._render_segments(mode, theme, segments)
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 rendered_highlighted return rendered_highlighted
# Create an ordered list of segments that can be dropped # 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] segments_priority = [segment for segment in sorted(segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0]
while self._total_len() > width and len(segments_priority): while self._total_len(segments) > width and len(segments_priority):
self.segments.remove(segments_priority[0]) segments.remove(segments_priority[0])
segments_priority.pop(0) segments_priority.pop(0)
# Do another render pass so we can calculate the correct amount of filler space # Do another render pass so we can calculate the correct amount of filler space
self._render_segments(mode, False) self._render_segments(mode, theme, segments, render_highlighted=False)
# Distribute the remaining space on the filler segments # Distribute the remaining space on the filler segments
segments_fillers = [segment for segment in self.segments if segment['type'] == 'filler'] segments_fillers = [segment for segment in segments if segment['type'] == 'filler']
if segments_fillers: if segments_fillers:
segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len()), len(segments_fillers)) segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len(segments)), len(segments_fillers))
segments_fillers_contents = ' ' * segments_fillers_len segments_fillers_contents = ' ' * segments_fillers_len
for segment in segments_fillers: for segment in segments_fillers:
segment['contents'] = segments_fillers_contents segment['contents'] = segments_fillers_contents
# Add remainder whitespace to the first filler segment # Add remainder whitespace to the first filler segment
segments_fillers[0]['contents'] += ' ' * segments_fillers_remainder segments_fillers[0]['contents'] += ' ' * segments_fillers_remainder
return self._render_segments(mode) return self._render_segments(mode, theme, segments)
def _render_segments(self, mode, render_highlighted=True): def _render_segments(self, mode, theme, segments, render_highlighted=True):
'''Internal segment rendering method. '''Internal segment rendering method.
This method loops through the segment array and compares the This method loops through the segment array and compares the
@ -62,18 +79,18 @@ class Renderer(object):
statusline if render_highlighted is True. statusline if render_highlighted is True.
''' '''
rendered_highlighted = u'' rendered_highlighted = u''
segments_len = len(self.segments) segments_len = len(segments)
mode = mode if mode in self.segments[0]['highlight'] else Colorscheme.DEFAULT_MODE_KEY mode = mode if mode in segments[0]['highlight'] else Colorscheme.DEFAULT_MODE_KEY
for index, segment in enumerate(self.segments): for index, segment in enumerate(segments):
prev_segment = self.segments[index - 1] if index > 0 else self.theme.EMPTY_SEGMENT prev_segment = segments[index - 1] if index > 0 else theme.EMPTY_SEGMENT
next_segment = self.segments[index + 1] if index < segments_len - 1 else self.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 segment['side'] == 'left' else prev_segment
segment['rendered_raw'] = u'' segment['rendered_raw'] = u''
outer_padding = ' ' if index == 0 or index == segments_len - 1 else '' 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_type = 'soft' if compare_segment['highlight'][mode]['bg'] == segment['highlight'][mode]['bg'] else 'hard'
divider = self.theme.get_divider(segment['side'], divider_type) divider = theme.get_divider(segment['side'], divider_type)
divider_hl = '' divider_hl = ''
segment_hl = '' segment_hl = ''
@ -109,13 +126,13 @@ class Renderer(object):
return rendered_highlighted return rendered_highlighted
def _total_len(self): def _total_len(self, segments):
'''Return total/rendered length of all segments. '''Return total/rendered length of all segments.
This method uses the rendered_raw property of the segments and requires This method uses the rendered_raw property of the segments and requires
that the segments have been rendered using the render() method first. that the segments have been rendered using the render() method first.
''' '''
return len(''.join([segment['rendered_raw'] for segment in self.segments])) return len(''.join([segment['rendered_raw'] for segment in segments]))
def hl(self, fg=None, bg=None, attr=None): def hl(self, fg=None, bg=None, attr=None):
raise NotImplementedError raise NotImplementedError

View File

@ -2,13 +2,12 @@
from importlib import import_module from importlib import import_module
import sys import sys
import os
class Segments(object): class Segments(object):
def __init__(self, ext, common_config, colorscheme): def __init__(self, ext, path, colorscheme):
self.ext = ext self.ext = ext
self.path = [os.path.expanduser(path) for path in common_config.get('paths', [])] self.path = path
self.colorscheme = colorscheme self.colorscheme = colorscheme
def get_function(self, segment): def get_function(self, segment):

View File

@ -1,10 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from powerline.segments import Segments
class Theme(object): class Theme(object):
def __init__(self, ext, colorscheme, theme_config, common_config): def __init__(self, ext, colorscheme, theme_config, common_config, get_segment):
self.colorscheme = colorscheme self.colorscheme = colorscheme
self.dividers = theme_config.get('dividers', common_config['dividers']) self.dividers = theme_config.get('dividers', common_config['dividers'])
self.segments = [] self.segments = []
@ -14,8 +12,6 @@ class Theme(object):
'highlight': {self.colorscheme.DEFAULT_MODE_KEY: {'fg': (False, False), 'bg': (False, False), 'attr': 0}} 'highlight': {self.colorscheme.DEFAULT_MODE_KEY: {'fg': (False, False), 'bg': (False, False), 'attr': 0}}
} }
get_segment = Segments(ext, common_config, colorscheme).get
for side in ['left', 'right']: for side in ['left', 'right']:
self.segments.extend((get_segment(segment, side) for segment in theme_config['segments'].get(side, []))) self.segments.extend((get_segment(segment, side) for segment in theme_config['segments'].get(side, [])))

View File

@ -0,0 +1,33 @@
{
"segments": {
"left": [
{
"name": "file_name",
"draw_divider": false
},
{
"type": "filler",
"highlight": ["background"]
}
],
"right": [
{
"name": "line_percent",
"args": { "gradient": true },
"priority": 30,
"after": "",
"rjust": 4
},
{
"type": "string",
"contents": "⭡ ",
"highlight": ["line_current_symbol", "line_current"]
},
{
"name": "line_current",
"draw_divider": false,
"rjust": 3
}
]
}
}