mirror of
https://github.com/powerline/powerline.git
synced 2025-07-23 22:05:43 +02:00
Rewrite core code to only allow a single-dimensional segment array
The segment tree seemed like a good idea at the time, but for the core code it's probably best to do a single-dimensional array and instead have some duplicated data (common segment attributes). By removing the tree structured segments the code is much simpler to understand and use, and probably quite a bit faster. If a tree structure is wanted for ease of configuration, it could easily be supported in the JSON config files for vim statuslines, and then letting the vim statusline code flatten the configuration into a single-dimensional array for rendering.
This commit is contained in:
parent
5abfdaeef8
commit
2ea555b2dd
185
lib/core.py
185
lib/core.py
@ -1,30 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
class Segment:
|
||||
'''Powerline segment renderer.
|
||||
|
||||
Powerline segments are initially structured as a tree of segments and sub
|
||||
segments. This is to give the segments a sense of grouping and "scope", to
|
||||
avoid having to define all the properties (fg, bg, etc.) for every single
|
||||
segment. By grouping you can e.g. provide a common background color for
|
||||
several segments.
|
||||
|
||||
Usage example:
|
||||
|
||||
from lib.core import Segment
|
||||
from lib.renderers import TerminalSegmentRenderer
|
||||
|
||||
powerline = Segment([
|
||||
Segment('First segment'),
|
||||
Segment([
|
||||
Segment('Grouped segment 1'),
|
||||
Segment('Grouped segment 2'),
|
||||
]),
|
||||
])
|
||||
|
||||
print(powerline.render(TerminalSegmentRenderer))
|
||||
'''
|
||||
class Powerline:
|
||||
dividers = {
|
||||
'l': {
|
||||
'hard': '⮀',
|
||||
@ -36,91 +13,16 @@ class Segment:
|
||||
},
|
||||
}
|
||||
|
||||
ATTR_BOLD = 1
|
||||
ATTR_ITALIC = 2
|
||||
ATTR_UNDERLINE = 4
|
||||
|
||||
def __init__(self, contents=None, fg=None, bg=None, attr=None, side=None, draw_divider=None, priority=None, filler=None):
|
||||
'''Create a new Powerline segment.
|
||||
def __init__(self, segments):
|
||||
'''Create a new Powerline.
|
||||
'''
|
||||
self.parent = None
|
||||
|
||||
self.contents = contents or ''
|
||||
self.fg = fg
|
||||
self.bg = bg
|
||||
self.attr = attr
|
||||
self.side = side
|
||||
self.draw_divider = draw_divider
|
||||
self.priority = priority
|
||||
self.filler = filler
|
||||
|
||||
if self.filler:
|
||||
# Filler segments should never have any dividers
|
||||
self.draw_divider = False
|
||||
|
||||
# Set the parent property for child segments
|
||||
for segment in self.contents:
|
||||
try:
|
||||
segment.parent = self
|
||||
except AttributeError:
|
||||
# Not a Segment node
|
||||
continue
|
||||
|
||||
def init_attributes(self):
|
||||
'''Initialize the default attributes for this segment.
|
||||
|
||||
This method is intended to be run when all segments in the segment tree
|
||||
have the correct parent segment set (i.e. after the root segment has
|
||||
been instantiated).
|
||||
'''
|
||||
def lookup_attr(attr, default, obj=self):
|
||||
'''Looks up attributes in the segment tree.
|
||||
|
||||
If the attribute isn't found anywhere, the default argument is used
|
||||
for this segment.
|
||||
'''
|
||||
# Check if the current object has the attribute defined
|
||||
obj_attr = getattr(obj, attr)
|
||||
if obj_attr is None:
|
||||
try:
|
||||
# Check if the object's parent has the attribute defined
|
||||
return lookup_attr(attr, default, obj.parent)
|
||||
except AttributeError:
|
||||
# Root node reached
|
||||
return default
|
||||
return obj_attr
|
||||
|
||||
# Set default attributes
|
||||
self.fg = lookup_attr('fg', False)
|
||||
self.bg = lookup_attr('bg', False)
|
||||
self.attr = lookup_attr('attr', False)
|
||||
self.side = lookup_attr('side', 'l')
|
||||
self.draw_divider = lookup_attr('draw_divider', True)
|
||||
self.priority = lookup_attr('priority', -1)
|
||||
self.filler = lookup_attr('filler', False)
|
||||
|
||||
try:
|
||||
if len(self.fg) == 2:
|
||||
self.fg = self.fg
|
||||
except TypeError:
|
||||
# Only the terminal color is defined, so we need to get the hex color
|
||||
from lib.colors import cterm_to_hex
|
||||
self.fg = [self.fg, cterm_to_hex(self.fg)]
|
||||
|
||||
try:
|
||||
if len(self.bg) == 2:
|
||||
self.bg = self.bg
|
||||
except TypeError:
|
||||
# Only the terminal color is defined, so we need to get the hex color
|
||||
from lib.colors import cterm_to_hex
|
||||
self.bg = [self.bg, cterm_to_hex(self.bg)]
|
||||
self.segments = segments
|
||||
|
||||
def render(self, renderer, width=None):
|
||||
'''Render the segment and all child segments.
|
||||
'''Render all the segments with the specified renderer.
|
||||
|
||||
This method flattens the segment and all its child segments into
|
||||
a one-dimensional array. It then loops through this array and compares
|
||||
the foreground/background colors and divider properties and returns the
|
||||
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
|
||||
@ -129,28 +31,13 @@ class Segment:
|
||||
provided they will fill the remaining space until the desired width is
|
||||
reached.
|
||||
'''
|
||||
def flatten(segment):
|
||||
'''Flatten the segment tree into a one-dimensional array.
|
||||
'''
|
||||
ret = []
|
||||
for child_segment in segment.contents:
|
||||
if isinstance(child_segment.contents, str):
|
||||
# If the contents of the child segment is a string then
|
||||
# this is a tree node
|
||||
child_segment.init_attributes()
|
||||
ret.append(child_segment)
|
||||
else:
|
||||
# This is a segment group that should be flattened
|
||||
ret += flatten(child_segment)
|
||||
return ret
|
||||
|
||||
segments = flatten(self)
|
||||
|
||||
def render_segments(segments, render_raw=True, render_highlighted=True):
|
||||
'''Render a one-dimensional segment array.
|
||||
'''Render a segment array.
|
||||
|
||||
By default this function renders both raw (un-highlighted segments
|
||||
used for calculating final width) and 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_raw = ''
|
||||
rendered_highlighted = ''
|
||||
@ -207,29 +94,67 @@ class Segment:
|
||||
'raw': rendered_raw,
|
||||
}
|
||||
|
||||
rendered = render_segments(segments)
|
||||
rendered = 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(segments, key=lambda segment: segment.priority, reverse=True) if segment.priority > 0]
|
||||
segments_priority = [segment for segment in sorted(self.segments, key=lambda segment: segment.priority, reverse=True) if segment.priority > 0]
|
||||
|
||||
while len(rendered['raw'].decode('utf-8')) > width and len(segments_priority):
|
||||
segments.remove(segments_priority[0])
|
||||
self.segments.remove(segments_priority[0])
|
||||
segments_priority.pop(0)
|
||||
|
||||
rendered = render_segments(segments, render_highlighted=False)
|
||||
rendered = render_segments(self.segments, render_highlighted=False)
|
||||
|
||||
# Distribute the remaining space on the filler segments
|
||||
segments_fillers = [segment for segment in segments if segment.filler is True]
|
||||
segments_fillers = [segment for segment in self.segments if segment.filler is True]
|
||||
if segments_fillers:
|
||||
segments_fillers_contents = ' ' * int((width - len(rendered['raw'].decode('utf-8'))) / len(segments_fillers))
|
||||
for segment in segments_fillers:
|
||||
segment.contents = segments_fillers_contents
|
||||
|
||||
# Do a final render now that we have handled the cropping and padding
|
||||
rendered = render_segments(segments, render_raw=False)
|
||||
rendered = render_segments(self.segments, render_raw=False)
|
||||
|
||||
return rendered['highlighted']
|
||||
|
||||
|
||||
class Segment:
|
||||
ATTR_BOLD = 1
|
||||
ATTR_ITALIC = 2
|
||||
ATTR_UNDERLINE = 4
|
||||
|
||||
def __init__(self, contents=None, fg=False, bg=False, attr=False, side='l', draw_divider=True, priority=-1, filler=False):
|
||||
'''Create a new Powerline segment.
|
||||
'''
|
||||
self.contents = str(contents or '')
|
||||
self.fg = fg
|
||||
self.bg = bg
|
||||
self.attr = attr
|
||||
self.side = side
|
||||
self.draw_divider = draw_divider
|
||||
self.priority = priority
|
||||
self.filler = filler
|
||||
|
||||
if self.filler:
|
||||
# Filler segments should never have any dividers
|
||||
self.draw_divider = False
|
||||
|
||||
try:
|
||||
if len(self.fg) != 2:
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
# Only the terminal color is defined, so we need to get the hex color
|
||||
from lib.colors import cterm_to_hex
|
||||
self.fg = [self.fg, cterm_to_hex(self.fg)]
|
||||
|
||||
try:
|
||||
if len(self.bg) != 2:
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
# Only the terminal color is defined, so we need to get the hex color
|
||||
from lib.colors import cterm_to_hex
|
||||
self.bg = [self.bg, cterm_to_hex(self.bg)]
|
||||
|
@ -2,17 +2,16 @@
|
||||
'''Powerline terminal prompt example.
|
||||
'''
|
||||
|
||||
from lib.core import Segment
|
||||
from lib.core import Powerline, Segment
|
||||
from lib.renderers import TerminalSegmentRenderer
|
||||
|
||||
powerline = Segment([
|
||||
powerline = Powerline([
|
||||
Segment('⭤ SSH', 220, 166, attr=Segment.ATTR_BOLD),
|
||||
Segment('username', 153, 31),
|
||||
Segment([
|
||||
Segment('~'),
|
||||
Segment('projects'),
|
||||
Segment('powerline', 231, attr=Segment.ATTR_BOLD),
|
||||
], 248, 239),
|
||||
Segment('~', 248, 239),
|
||||
Segment('projects', 248, 239),
|
||||
Segment('powerline', 231, 239, attr=Segment.ATTR_BOLD),
|
||||
Segment(filler=True),
|
||||
])
|
||||
|
||||
print(powerline.render(TerminalSegmentRenderer()))
|
||||
|
@ -12,7 +12,7 @@ import re
|
||||
import sys
|
||||
sys.path.append('.')
|
||||
|
||||
from lib.core import Segment
|
||||
from lib.core import Powerline, Segment
|
||||
from lib.renderers import VimSegmentRenderer
|
||||
|
||||
winwidth = int(vim.eval('winwidth(0)'))
|
||||
@ -47,26 +47,20 @@ filepath = os.path.split(vim.eval('expand("%:~:.")'))
|
||||
if filepath[0]:
|
||||
filepath[0] += os.sep
|
||||
|
||||
powerline = Segment([
|
||||
powerline = Powerline([
|
||||
Segment(mode, 22, 148, attr=Segment.ATTR_BOLD),
|
||||
Segment('⭠ ' + branch, 250, 240, priority=10),
|
||||
Segment([
|
||||
Segment(filepath[0], draw_divider=False, priority=5),
|
||||
Segment(filepath[1], 231, attr=Segment.ATTR_BOLD),
|
||||
], 250, 240),
|
||||
Segment(filler=True),
|
||||
Segment([
|
||||
Segment(vim.eval('&ff'), priority=50),
|
||||
Segment(vim.eval('&fenc'), priority=50),
|
||||
Segment(vim.eval('&ft'), priority=50),
|
||||
Segment(str(line_percent).rjust(3) + '%', line_percent_color, 240, priority=30),
|
||||
Segment([
|
||||
Segment('⭡ ', 239),
|
||||
Segment(str(line_current).rjust(3), attr=Segment.ATTR_BOLD, draw_divider=False),
|
||||
Segment(':' + str(col_current).ljust(2), 244, priority=30, draw_divider=False),
|
||||
], 235, 252),
|
||||
], 247, side='r'),
|
||||
], fg=236, bg=236)
|
||||
Segment(filepath[0], 250, 240, draw_divider=False, priority=5),
|
||||
Segment(filepath[1], 231, 240, attr=Segment.ATTR_BOLD),
|
||||
Segment(filler=True, fg=236, bg=236),
|
||||
Segment(vim.eval('&ff'), 247, 236, side='r', priority=50),
|
||||
Segment(vim.eval('&fenc'), 247, 236, side='r', priority=50),
|
||||
Segment(vim.eval('&ft'), 247, 236, side='r', priority=50),
|
||||
Segment(str(line_percent).rjust(3) + '%', line_percent_color, 240, side='r', priority=30),
|
||||
Segment('⭡ ', 239, 252, side='r'),
|
||||
Segment(str(line_current).rjust(3), 235, 252, attr=Segment.ATTR_BOLD, side='r', draw_divider=False),
|
||||
Segment(':' + str(col_current).ljust(2), 244, 252, side='r', priority=30, draw_divider=False),
|
||||
])
|
||||
|
||||
renderer = VimSegmentRenderer()
|
||||
stl = powerline.render(renderer, winwidth)
|
||||
|
Loading…
x
Reference in New Issue
Block a user