Merge branch 'feature/project-restructuring' into develop

This commit is contained in:
Kim Silkebækken 2012-12-13 13:18:27 +01:00
commit 98337d26ec
26 changed files with 772 additions and 482 deletions

View File

@ -7,16 +7,17 @@ import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from lib.core import Powerline, mksegment
from lib.renderers import TerminalSegmentRenderer
from powerline.core import Powerline
from powerline.segment import mksegment
from powerline.ext.terminal import TerminalRenderer
powerline = Powerline([
mksegment('⭤ SSH', 220, 166, attr=Powerline.ATTR_BOLD),
mksegment('⭤ SSH', 220, 166, attr=TerminalRenderer.ATTR_BOLD),
mksegment('username', 153, 31),
mksegment('~', 248, 239),
mksegment('projects', 248, 239),
mksegment('powerline', 231, 239, attr=Powerline.ATTR_BOLD),
mksegment('powerline', 231, 239, attr=TerminalRenderer.ATTR_BOLD),
mksegment(filler=True),
])
print(powerline.render(TerminalSegmentRenderer()))
print(powerline.render(TerminalRenderer))

View File

@ -9,15 +9,16 @@ import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from lib.core import Powerline, mksegment
from lib.renderers import TmuxSegmentRenderer
from powerline.core import Powerline
from powerline.segment import mksegment
from powerline.ext.tmux import TmuxRenderer
powerline = Powerline([
mksegment('⭤ SSH', 220, 166, attr=Powerline.ATTR_BOLD),
mksegment('⭤ SSH', 220, 166, attr=TmuxRenderer.ATTR_BOLD),
mksegment('username', 153, 31),
mksegment('23:45', 248, 239),
mksegment('10.0.0.110', 231, 239, attr=Powerline.ATTR_BOLD),
mksegment('10.0.0.110', 231, 239, attr=TmuxRenderer.ATTR_BOLD),
mksegment(filler=True, cterm_fg=236, cterm_bg=236),
])
print(powerline.render(TmuxSegmentRenderer()).encode('utf-8'))
print(powerline.render(TmuxRenderer).encode('utf-8'))

View File

@ -1,197 +0,0 @@
# -*- coding: utf-8 -*-
import vim
import os
from lib.core import Powerline, mksegment
from lib.renderers import VimSegmentRenderer
modes = {
'n': 'NORMAL',
'no': 'N·OPER',
'v': 'VISUAL',
'V': 'V·LINE',
'': 'V·BLCK',
's': 'SELECT',
'S': 'S·LINE',
'': 'S·BLCK',
'i': 'INSERT',
'R': 'REPLACE',
'Rv': 'V·RPLCE',
'c': 'COMMND',
'cv': 'VIM EX',
'ce': 'EX',
'r': 'PROMPT',
'rm': 'MORE',
'r?': 'CONFIRM',
'!': 'SHELL',
}
# We need to replace this private use glyph with a double-percent later
percent_placeholder = u''
if hasattr(vim, 'bindeval'):
# This branch is used to avoid invoking vim parser as much as possible
def get_vim_func(f, rettype=None):
try:
return vim.bindeval('function("' + f + '")')
except vim.error:
return None
vim_globals = vim.bindeval('g:')
def set_global_var(var, val):
vim_globals[var] = val
else:
import json
class VimFunc(object):
__slots__ = ('f', 'rettype')
def __init__(self, f, rettype=None):
self.f = f
self.rettype = rettype
def __call__(self, *args):
r = vim.eval(self.f + '(' + json.dumps(args)[1:-1] + ')')
if self.rettype:
return self.rettype(r)
return r
get_vim_func = VimFunc
def set_global_var(var, val): # NOQA
vim.command('let g:{0}={1}'.format(var, json.dumps(val)))
vim_funcs = {
'winwidth': get_vim_func('winwidth', rettype=int),
'mode': get_vim_func('mode'),
'fghead': get_vim_func('fugitive#head'),
'line': get_vim_func('line', rettype=int),
'col': get_vim_func('col', rettype=int),
'expand': get_vim_func('expand'),
'tbcurtag': get_vim_func('tagbar#currenttag'),
'hlexists': get_vim_func('hlexists', rettype=int),
}
getwinvar = get_vim_func('getwinvar')
setwinvar = get_vim_func('setwinvar')
def statusline(winnr):
winwidth = vim_funcs['winwidth'](winnr)
current = getwinvar(winnr, 'current')
windata = getwinvar(winnr, 'powerline')
if current:
# Recreate segment data for each redraw if we're in the current window
line_current = vim_funcs['line']('.')
line_end = vim_funcs['line']('$')
line_percent = line_current * 100 // line_end
try:
branch = vim_funcs['fghead'](5)
except vim.error:
vim_funcs['fghead'] = None
branch = ''
except TypeError:
branch = ''
if branch:
branch = u'' + branch
# Fun gradient colored percent segment
line_percent_gradient = [160, 166, 172, 178, 184, 190]
line_percent_color = line_percent_gradient[int((len(line_percent_gradient) - 1) * line_percent / 100)]
col_current = vim_funcs['col']('.')
filepath, filename = os.path.split(vim_funcs['expand']('%:~:.'))
filename_color = 231
if filepath:
filepath += os.sep
if not filename:
filename = '[No Name]'
filename_color = 250
readonly = vim.eval('&ro ? "" : ""')
modified = vim.eval('&mod ? " +" : ""')
try:
currenttag = vim_funcs['tbcurtag']('%s', '')
except vim.error:
vim_funcs['tbcurtag'] = None
currenttag = ''
except TypeError:
currenttag = ''
windata = {
'paste': vim.eval('&paste ? "PASTE" : ""'),
'branch': branch,
'readonly': readonly,
'filepath': filepath,
'filename': filename,
'filename_color': filename_color,
'modified': modified,
'currenttag': currenttag,
'fileformat': vim.eval('&ff'),
'fileencoding': vim.eval('&fenc'),
'filetype': vim.eval('&ft'),
'line_percent': str(line_percent).rjust(3) + percent_placeholder,
'line_percent_color': line_percent_color,
'linecurrent': str(line_current).rjust(3),
'colcurrent': ':' + str(col_current).ljust(2),
}
setwinvar(winnr, 'powerline', windata)
mode = modes[vim_funcs['mode']()]
if not current:
mode = None
powerline = Powerline([
mksegment(mode, 22, 148, attr=Powerline.ATTR_BOLD),
mksegment(windata['paste'], 231, 166, attr=Powerline.ATTR_BOLD),
mksegment(windata['branch'], 250, 240, priority=60),
mksegment(windata['readonly'], 196, 240, draw_divider=False),
mksegment(windata['filepath'], 250, 240, draw_divider=False, priority=40),
mksegment(windata['filename'], windata['filename_color'], 240, attr=Powerline.ATTR_BOLD, draw_divider=False),
mksegment(windata['modified'], 220, 240, attr=Powerline.ATTR_BOLD),
mksegment(windata['currenttag'], 246, 236, draw_divider=False, priority=100),
mksegment(filler=True, cterm_fg=236, cterm_bg=236),
mksegment(windata['fileformat'], 247, 236, side='r', priority=50, draw_divider=False),
mksegment(windata['fileencoding'], 247, 236, side='r', priority=50),
mksegment(windata['filetype'], 247, 236, side='r', priority=50),
mksegment(windata['line_percent'], windata['line_percent_color'], 240, side='r', priority=30),
mksegment(u'', 239, 252, side='r'),
mksegment(windata['linecurrent'], 235, 252, attr=Powerline.ATTR_BOLD, side='r', draw_divider=False),
mksegment(windata['colcurrent'], 244, 252, side='r', priority=30, draw_divider=False),
])
renderer = VimSegmentRenderer()
stl = powerline.render(renderer, winwidth)
# Replace percent placeholders
stl = stl.replace(percent_placeholder, '%%')
# Create highlighting groups
for idx, hl in renderer.hl_groups.items():
if vim_funcs['hlexists'](hl['name']):
# Only create hl group if it doesn't already exist
continue
vim.command('hi {group} ctermfg={ctermfg} guifg={guifg} guibg={guibg} ctermbg={ctermbg} cterm={attr} gui={attr}'.format(
group=hl['name'],
ctermfg=hl['ctermfg'],
guifg='#{0:06x}'.format(hl['guifg']) if hl['guifg'] != 'NONE' else 'NONE',
ctermbg=hl['ctermbg'],
guibg='#{0:06x}'.format(hl['guibg']) if hl['guibg'] != 'NONE' else 'NONE',
attr=','.join(hl['attr']),
))
return stl
# vim: ft=python ts=4 sts=4 sw=4 noet

View File

@ -3,7 +3,9 @@
python import sys, vim, os
python sys.path.append(vim.eval('expand("<sfile>:h:h:h")'))
python from examples.vim.powerline import statusline
python from examples.vim.pl import statusline
python from powerline.core import Powerline
python pl = Powerline('vim')
if exists('*pyeval')
let s:pyeval = function('pyeval')
@ -15,7 +17,7 @@ else
endif
function! Powerline(winnr)
return s:pyeval('statusline('. a:winnr .')')
return s:pyeval('pl.renderer.render('. a:winnr .')')
endfunction
function! s:WinDoPowerline()

View File

@ -1,162 +0,0 @@
# -*- coding: utf-8 -*-
from lib.colors import cterm_to_hex
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.
Segments that have empty contents and aren't filler segments are
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.
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]))
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.
'''
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

@ -1,7 +0,0 @@
class SegmentRenderer:
def hl(self, fg=None, bg=None, attr=None):
raise NotImplementedError
from lib.renderers.terminal import TerminalSegmentRenderer
from lib.renderers.tmux import TmuxSegmentRenderer
from lib.renderers.vim import VimSegmentRenderer

View File

@ -1,60 +0,0 @@
#!/usr/bin/env python
from lib.core import Powerline
from lib.renderers import SegmentRenderer
class VimSegmentRenderer(SegmentRenderer):
'''Powerline vim segment renderer.
'''
def __init__(self):
self.hl_groups = {}
def hl(self, fg=None, bg=None, attr=None):
'''Highlight a segment.
If an argument is None, the argument is ignored. If an argument is
False, the argument is reset to the terminal defaults. If an argument
is a valid color or attribute, it's added to the vim highlight group.
'''
# We don't need to explicitly reset attributes in vim, so skip those calls
if not attr and not bg and not fg:
return ''
if not (fg, bg, attr) in self.hl_groups:
hl_group = {
'ctermfg': 'NONE',
'guifg': 'NONE',
'ctermbg': 'NONE',
'guibg': 'NONE',
'attr': ['NONE'],
'name': '',
}
if fg is not None and fg is not False:
hl_group['ctermfg'] = fg[0]
hl_group['guifg'] = fg[1]
if bg is not None and bg is not False:
hl_group['ctermbg'] = bg[0]
hl_group['guibg'] = bg[1]
if attr:
hl_group['attr'] = []
if attr & Powerline.ATTR_BOLD:
hl_group['attr'].append('bold')
if attr & Powerline.ATTR_ITALIC:
hl_group['attr'].append('italic')
if attr & Powerline.ATTR_UNDERLINE:
hl_group['attr'].append('underline')
hl_group['name'] = 'Pl_' + \
str(hl_group['ctermfg']) + '_' + \
str(hl_group['guifg']) + '_' + \
str(hl_group['ctermbg']) + '_' + \
str(hl_group['guibg']) + '_' + \
''.join(hl_group['attr'])
self.hl_groups[(fg, bg, attr)] = hl_group
return '%#' + self.hl_groups[(fg, bg, attr)]['name'] + '#'

View File

@ -1,3 +1,105 @@
# -*- coding: utf-8 -*-
class Colorscheme(object):
default_mode_key = '__default__'
def __init__(self, colorscheme):
'''Initialize a colorscheme.
'''
self.colors = {}
self.modes_groups = {
self.default_mode_key: {}
}
# Create a dict of color tuples with both a cterm and hex value
for color_name, color in colorscheme['colors'].items():
try:
self.colors[color_name] = (color[0], color[1])
except TypeError:
self.colors[color_name] = (color, cterm_to_hex[color])
# Create highlighting groups for all modes
for group_name, group_props in colorscheme['groups'].items():
group_attr_flag = self._get_attr_flag(group_props.get('attr', []))
self.modes_groups[self.default_mode_key][group_name] = {
'fg': self.colors[group_props['fg']],
'bg': self.colors[group_props['bg']],
'attr': group_attr_flag,
}
# Create mode-specific highlighting for this group
for mode, translations in colorscheme['mode_translations'].items():
if not mode in self.modes_groups:
self.modes_groups[mode] = {}
if group_name in translations['groups']:
# Override entire group if present in the translations group dict
self.modes_groups[mode][group_name] = {
'fg': self.colors[translations['groups'][group_name]['fg']],
'bg': self.colors[translations['groups'][group_name]['bg']],
'attr': self._get_attr_flag(translations['groups'][group_name].get('attr', [])),
}
else:
# Fallback to color translations from the translations colors dict
self.modes_groups[mode][group_name] = {
'fg': self.colors[translations['colors'].get(group_props['fg'], group_props['fg'])],
'bg': self.colors[translations['colors'].get(group_props['bg'], group_props['bg'])],
'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.
If no mode is specified, or the mode doesn't exist, highlighting for
the default mode is returned.
'''
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):
'''Convert an attribute array to a renderer flag.
'''
from powerline.renderer import Renderer
attr_flag = 0
if 'bold' in attributes:
attr_flag |= Renderer.ATTR_BOLD
if 'italic' in attributes:
attr_flag |= Renderer.ATTR_ITALIC
if 'underline' in attributes:
attr_flag |= Renderer.ATTR_UNDERLINE
return attr_flag
cterm_to_hex = {
16: 0x000000, 17: 0x00005f, 18: 0x000087, 19: 0x0000af, 20: 0x0000d7, 21: 0x0000ff,
22: 0x005f00, 23: 0x005f5f, 24: 0x005f87, 25: 0x005faf, 26: 0x005fd7, 27: 0x005fff,

View File

@ -0,0 +1,126 @@
{
"name": "default",
"colors": {
"black": 16,
"white": 231,
"darkestgreen": 22,
"darkgreen": 28,
"mediumgreen": 70,
"brightgreen": 148,
"darkestcyan": 23,
"mediumcyan": 117,
"darkestblue": 24,
"darkblue": 31,
"darkestred": 52,
"darkred": 88,
"mediumred": 124,
"brightred": 160,
"brightestred": 196,
"darkestpurple": 55,
"mediumpurple": 98,
"brightpurple": 189,
"mediumorange": 166,
"brightorange": 208,
"brightestorange": 214,
"brightyellow": 220,
"gray0": 233,
"gray1": 235,
"gray2": 236,
"gray3": 239,
"gray4": 240,
"gray5": 241,
"gray6": 244,
"gray7": 245,
"gray8": 247,
"gray9": 250,
"gray10": 252,
"gradient1": 190,
"gradient2": 184,
"gradient3": 178,
"gradient4": 172,
"gradient5": 166,
"gradient6": 160
},
"groups": {
"background": { "fg": "white", "bg": "gray2" },
"mode": { "fg": "darkestgreen", "bg": "brightgreen", "attr": ["bold"] },
"modified_indicator": { "fg": "brightyellow", "bg": "gray4", "attr": ["bold"] },
"paste_indicator": { "fg": "white", "bg": "mediumorange", "attr": ["bold"] },
"readonly_indicator": { "fg": "brightestred", "bg": "gray4" },
"branch": { "fg": "gray9", "bg": "gray4" },
"file_directory": { "fg": "gray9", "bg": "gray4" },
"file_name": { "fg": "white", "bg": "gray4", "attr": ["bold"] },
"file_name_empty": { "fg": "gray9", "bg": "gray4" },
"file_format": { "fg": "gray8", "bg": "gray2" },
"file_encoding": { "fg": "gray8", "bg": "gray2" },
"file_type": { "fg": "gray8", "bg": "gray2" },
"line_percent": { "fg": "gray9", "bg": "gray4" },
"line_percent_gradient1": { "fg": "gradient1", "bg": "gray4" },
"line_percent_gradient2": { "fg": "gradient2", "bg": "gray4" },
"line_percent_gradient3": { "fg": "gradient3", "bg": "gray4" },
"line_percent_gradient4": { "fg": "gradient4", "bg": "gray4" },
"line_percent_gradient5": { "fg": "gradient5", "bg": "gray4" },
"line_percent_gradient6": { "fg": "gradient6", "bg": "gray4" },
"line_current": { "fg": "gray1", "bg": "gray10", "attr": ["bold"] },
"line_current_symbol": { "fg": "gray1", "bg": "gray10" },
"col_current": { "fg": "gray6", "bg": "gray10" }
},
"mode_translations": {
"nc": {
"colors": {
"gray0": "gray0",
"gray1": "gray0",
"gray2": "gray0",
"gray3": "gray1",
"gray4": "gray1",
"gray5": "gray1",
"gray6": "gray1",
"gray7": "gray4",
"gray8": "gray4",
"gray9": "gray4",
"gray10": "gray5",
"white": "gray6",
"gradient1": "gray5",
"gradient2": "gray5",
"gradient3": "gray5",
"gradient4": "gray5",
"gradient5": "gray5",
"gradient6": "gray5"
},
"groups": {}
},
"i": {
"colors": {
"gray0": "darkestblue",
"gray1": "darkestblue",
"gray2": "darkestblue",
"gray3": "darkblue",
"gray4": "darkblue",
"gray5": "darkestcyan",
"gray6": "darkestcyan",
"gray7": "darkestcyan",
"gray8": "mediumcyan",
"gray9": "mediumcyan",
"gray10": "mediumcyan",
"gradient1": "mediumcyan",
"gradient2": "mediumcyan",
"gradient3": "mediumcyan",
"gradient4": "mediumcyan",
"gradient5": "mediumcyan",
"gradient6": "mediumcyan"
},
"groups": {
"mode": { "fg": "darkestcyan", "bg": "white", "attr": ["bold"] }
}
}
}
}

20
powerline/config.json Normal file
View File

@ -0,0 +1,20 @@
{
"common": {
"dividers": {
"left": {
"hard": "⮀",
"soft": "⮁"
},
"right": {
"hard": "⮂",
"soft": "⮃"
}
}
},
"ext": {
"vim": {
"colorscheme": "default",
"theme": "default"
}
}
}

52
powerline/core.py Normal file
View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import importlib
import json
import os
import sys
from colorscheme import Colorscheme
from theme import Theme
class Powerline(object):
def __init__(self, ext):
try:
config_home = os.environ['XDG_CONFIG_HOME']
except KeyError:
config_home = os.path.expanduser('~/.config')
config_path = os.path.join(config_home, 'powerline')
plugin_path = os.path.realpath(os.path.dirname(__file__))
self.search_paths = [config_path, plugin_path]
sys.path[:0] = self.search_paths
# Load main config file
config = self._load_json_config('config')
self.config = config['common']
self.config_ext = config['ext'][ext]
# Load and initialize colorscheme
colorscheme_config = self._load_json_config(os.path.join('colorschemes', self.config_ext['colorscheme']))
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, 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.theme)
def _load_json_config(self, config_file):
config_file += '.json'
for path in self.search_paths:
config_file_path = os.path.join(path, config_file)
if os.path.isfile(config_file_path):
with open(config_file_path, 'rb') as config_file_fp:
return json.load(config_file_fp)
raise IOError('Config file not found in search path: {0}'.format(config_file))

View File

@ -0,0 +1 @@
from renderer import TerminalRenderer # NOQA

View File

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

View File

@ -0,0 +1 @@
from renderer import TmuxRenderer # NOQA

View File

@ -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']

View File

@ -0,0 +1 @@
from renderer import VimRenderer # NOQA

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
from powerline.ext.vim.bindings import vim_get_func
from powerline.renderer import Renderer
import vim
vim_mode = vim_get_func('mode')
vim_winwidth = vim_get_func('winwidth', rettype=int)
vim_getwinvar = vim_get_func('getwinvar')
vim_setwinvar = vim_get_func('setwinvar')
class VimRenderer(Renderer):
'''Powerline vim segment renderer.
'''
PERCENT_PLACEHOLDER = u''
def __init__(self, theme):
super(VimRenderer, self).__init__(theme)
self.hl_groups = {}
def render(self, winnr):
'''Render all segments.
This method handles replacing of the percent placeholder for vim
statuslines, and it caches segment contents which are retrieved and
used in non-current windows.
'''
current = vim_getwinvar(winnr, 'current')
winwidth = vim_winwidth(winnr)
if current or not vim_getwinvar(winnr, 'powerline'):
contents_cached = {segment['key']: segment['contents'] for segment in self.segments if segment['type'] == 'function'}
vim_setwinvar(winnr, 'powerline', contents_cached)
if current:
mode = vim_mode()
contents_override = None
else:
mode = 'nc'
contents_cached = vim_getwinvar(winnr, 'powerline')
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 = statusline.replace(self.PERCENT_PLACEHOLDER, '%%')
return statusline
def hl(self, fg=None, bg=None, attr=None):
'''Highlight a segment.
If an argument is None, the argument is ignored. If an argument is
False, the argument is reset to the terminal defaults. If an argument
is a valid color or attribute, it's added to the vim highlight group.
'''
# We don't need to explicitly reset attributes in vim, so skip those calls
if not attr and not bg and not fg:
return ''
if not (fg, bg, attr) in self.hl_groups:
hl_group = {
'ctermfg': 'NONE',
'guifg': 'NONE',
'ctermbg': 'NONE',
'guibg': 'NONE',
'attr': ['NONE'],
'name': '',
}
if fg is not None and fg is not False:
hl_group['ctermfg'] = fg[0]
hl_group['guifg'] = fg[1]
if bg is not None and bg is not False:
hl_group['ctermbg'] = bg[0]
hl_group['guibg'] = bg[1]
if attr:
hl_group['attr'] = []
if attr & self.ATTR_BOLD:
hl_group['attr'].append('bold')
if attr & self.ATTR_ITALIC:
hl_group['attr'].append('italic')
if attr & self.ATTR_UNDERLINE:
hl_group['attr'].append('underline')
hl_group['name'] = 'Pl_' + \
str(hl_group['ctermfg']) + '_' + \
str(hl_group['guifg']) + '_' + \
str(hl_group['ctermbg']) + '_' + \
str(hl_group['guibg']) + '_' + \
''.join(hl_group['attr'])
self.hl_groups[(fg, bg, attr)] = hl_group
# Create highlighting group in vim
vim.command('hi {group} ctermfg={ctermfg} guifg={guifg} guibg={guibg} ctermbg={ctermbg} cterm={attr} gui={attr}'.format(
group=hl_group['name'],
ctermfg=hl_group['ctermfg'],
guifg='#{0:06x}'.format(hl_group['guifg']) if hl_group['guifg'] != 'NONE' else 'NONE',
ctermbg=hl_group['ctermbg'],
guibg='#{0:06x}'.format(hl_group['guibg']) if hl_group['guibg'] != 'NONE' else 'NONE',
attr=','.join(hl_group['attr']),
))
return '%#' + self.hl_groups[(fg, bg, attr)]['name'] + '#'

View File

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),
@ -15,24 +16,24 @@ vim_funcs = {
}
vim_modes = {
'n': 'NORMAL',
'no': 'N·OPER',
'v': 'VISUAL',
'V': 'V·LINE',
'': 'V·BLCK',
's': 'SELECT',
'S': 'S·LINE',
'': 'S·BLCK',
'i': 'INSERT',
'R': 'REPLACE',
'Rv': 'V·RPLCE',
'c': 'COMMND',
'cv': 'VIM EX',
'ce': 'EX',
'r': 'PROMPT',
'rm': 'MORE',
'r?': 'CONFIRM',
'!': 'SHELL',
'n': u'NORMAL',
'no': u'N·OPER',
'v': u'VISUAL',
'V': u'V·LINE',
'': u'V·BLCK',
's': u'SELECT',
'S': u'S·LINE',
'': u'S·BLCK',
'i': u'INSERT',
'R': u'REPLACE',
'Rv': u'V·RPLCE',
'c': u'COMMND',
'cv': u'VIM EX',
'ce': u'EX',
'r': u'PROMPT',
'rm': u'MORE',
'r?': u'CONFIRM',
'!': u'SHELL',
}
@ -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'+'):
@ -74,7 +75,7 @@ def readonly_indicator(text=u'⭤'):
return text if int(vim.eval('&readonly')) else None
def branch(symbol=u''):
def branch():
'''Return VCS branch.
TODO: Expand this function to handle several VCS plugins.
@ -87,13 +88,13 @@ def branch(symbol=u'⭠'):
except TypeError:
pass
return symbol + ' ' + branch if branch else None
return branch if branch else None
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():

121
powerline/renderer.py Normal file
View File

@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
from colorscheme import Colorscheme
class Renderer(object):
ATTR_BOLD = 1
ATTR_ITALIC = 2
ATTR_UNDERLINE = 4
def __init__(self, theme):
self.segments = []
self.theme = theme
def render(self, mode, width=None, contents_override=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 filler segments are
provided they will fill the remaining space until the desired width is
reached.
'''
self.segments = self.theme.get_segments(mode, contents_override)
rendered_highlighted = self._render_segments(mode)
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
self._render_segments(mode, False)
# Distribute the remaining space on the filler segments
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
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 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)
mode = mode if mode in self.segments[0]['highlight'] else Colorscheme.default_mode_key
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.
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

92
powerline/theme.py Normal file
View File

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
import importlib
class Theme(object):
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')
segment_module = segment.get('module', 'core')
if segment_type == 'function':
# Import segment function and assign it to the contents
function_module = 'powerline.ext.{0}.segments.{1}'.format(ext, segment_module)
function_name = segment['name']
contents_func = getattr(importlib.import_module(function_module), function_name)
elif segment_type == 'string':
contents = segment.get('contents')
elif segment_type == 'filler':
pass
else:
raise TypeError('Unknown segment type: {0}'.format(segment_type))
highlighting_group = segment.get('highlight', segment.get('name'))
self.segments.append({
'key': None if segment_type != 'function' else '{0}.{1}'.format(segment_module, function_name),
'type': segment_type,
'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),
'rjust': segment.get('rjust', False),
'priority': segment.get('priority', -1),
'draw_divider': segment.get('draw_divider', True),
'side': side,
'exclude_modes': segment.get('exclude_modes', []),
'include_modes': segment.get('include_modes', []),
})
def get_divider(self, side='left', type='soft'):
'''Return segment divider.
'''
return self.dividers[side][type]
def get_segments(self, mode, contents_override=None):
'''Return all segments.
Function segments are called, and all segments get their before/after
and ljust/rjust properties applied.
'''
contents_override = contents_override or {}
return_segments = []
for segment in self.segments:
if mode in segment['exclude_modes'] or (segment['include_modes'] and segment not in segment['include_modes']):
continue
if segment['type'] == 'function':
contents = contents_override.get(segment['key'], segment['contents_func'](**segment['args']))
if contents is None:
continue
try:
segment['highlight'] = self.colorscheme.get_group_highlighting(contents['highlight'])
segment['contents'] = contents['contents']
except TypeError:
segment['contents'] = contents
elif segment['type'] == 'filler' or (segment['type'] == 'string' and segment['contents'] is not None):
pass
else:
continue
if not segment['key'] in contents_override:
# Only apply before/after/just to non-overridden segments
segment['contents'] = unicode(segment['before'] + unicode(segment['contents']) + segment['after'])\
.ljust(segment['ljust'])\
.rjust(segment['rjust'])
return_segments.append(segment)
return return_segments

View File

@ -0,0 +1,88 @@
{
"name": "default",
"segments": {
"left": [
{
"name": "mode",
"exclude_modes": ["nc"]
},
{
"name": "paste_indicator",
"exclude_modes": ["nc"],
"priority": 10
},
{
"name": "branch",
"exclude_modes": ["nc"],
"priority": 60,
"before": "⭠ "
},
{
"name": "readonly_indicator",
"exclude_modes": ["nc"],
"draw_divider": false
},
{
"name": "file_directory",
"priority": 40,
"draw_divider": false
},
{
"name": "file_name",
"draw_divider": false
},
{
"name": "modified_indicator",
"args": { "text": "+" },
"exclude_modes": ["nc"],
"before": " "
},
{
"type": "filler",
"highlight": ["background"]
}
],
"right": [
{
"name": "file_format",
"draw_divider": false,
"exclude_modes": ["nc"],
"priority": 50
},
{
"name": "file_encoding",
"exclude_modes": ["nc"],
"priority": 50
},
{
"name": "file_type",
"exclude_modes": ["nc"],
"priority": 50
},
{
"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
},
{
"name": "col_current",
"draw_divider": false,
"priority": 30,
"before": ":",
"ljust": 3
}
]
}
}

View File

@ -1,5 +0,0 @@
# flake8: noqa
from core import (mode, modified_indicator, paste_indicator,
readonly_indicator, branch, file_directory, file_name, file_format,
file_encoding, file_type, line_percent, line_current, col_current)