mirror of
https://github.com/powerline/powerline.git
synced 2025-07-23 13:55:45 +02:00
Improve rendering performance
This change removes the Segment class as this takes forever to remove from the segment array when removing low-priority segments. It has instead been replaced by a wrapper function that works the same and returns a working dict of all segment properties. The regex substitution bottleneck in the vim example has been fixed by using a single-character percent placeholder in vim segments which is later replaced with a double percent using str.replace().
This commit is contained in:
parent
f4e3d01d07
commit
1d3c259070
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
|
|
||||||
from lib.core import Powerline, Segment
|
from lib.core import Powerline, Segment
|
||||||
from lib.renderers import TerminalSegmentRenderer
|
from lib.renderers import TerminalSegmentRenderer
|
||||||
|
@ -4,7 +4,7 @@ import vim
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from lib.core import Powerline, Segment
|
from lib.core import Powerline, mksegment
|
||||||
from lib.renderers import VimSegmentRenderer
|
from lib.renderers import VimSegmentRenderer
|
||||||
|
|
||||||
modes = {
|
modes = {
|
||||||
@ -28,6 +28,9 @@ modes = {
|
|||||||
'!': 'SHELL',
|
'!': 'SHELL',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# We need to replace this private use glyph with a double-percent later
|
||||||
|
percent_placeholder = ''.decode('utf-8')
|
||||||
|
|
||||||
if hasattr(vim, 'bindeval'):
|
if hasattr(vim, 'bindeval'):
|
||||||
# This branch is used to avoid invoking vim parser as much as possible
|
# This branch is used to avoid invoking vim parser as much as possible
|
||||||
|
|
||||||
@ -137,7 +140,7 @@ def statusline(winnr):
|
|||||||
'fileformat': vim.eval('&ff'),
|
'fileformat': vim.eval('&ff'),
|
||||||
'fileencoding': vim.eval('&fenc'),
|
'fileencoding': vim.eval('&fenc'),
|
||||||
'filetype': vim.eval('&ft'),
|
'filetype': vim.eval('&ft'),
|
||||||
'line_percent': str(line_percent).rjust(3) + '%',
|
'line_percent': str(line_percent).rjust(3) + percent_placeholder,
|
||||||
'line_percent_color': line_percent_color,
|
'line_percent_color': line_percent_color,
|
||||||
'linecurrent': str(line_current).rjust(3),
|
'linecurrent': str(line_current).rjust(3),
|
||||||
'colcurrent': ':' + str(col_current).ljust(2),
|
'colcurrent': ':' + str(col_current).ljust(2),
|
||||||
@ -151,29 +154,29 @@ def statusline(winnr):
|
|||||||
mode = None
|
mode = None
|
||||||
|
|
||||||
powerline = Powerline([
|
powerline = Powerline([
|
||||||
Segment(mode, 22, 148, attr=Segment.ATTR_BOLD),
|
mksegment(mode, 22, 148, attr=Powerline.ATTR_BOLD),
|
||||||
Segment(windata['paste'], 231, 166, attr=Segment.ATTR_BOLD),
|
mksegment(windata['paste'], 231, 166, attr=Powerline.ATTR_BOLD),
|
||||||
Segment(windata['branch'], 250, 240, priority=10),
|
mksegment(windata['branch'], 250, 240, priority=10),
|
||||||
Segment(windata['readonly'], 196, 240, draw_divider=False),
|
mksegment(windata['readonly'], 196, 240, draw_divider=False),
|
||||||
Segment(windata['filepath'], 250, 240, draw_divider=False, priority=5),
|
mksegment(windata['filepath'], 250, 240, draw_divider=False, priority=5),
|
||||||
Segment(windata['filename'], windata['filename_color'], 240, attr=Segment.ATTR_BOLD, draw_divider=not len(windata['modified'])),
|
mksegment(windata['filename'], windata['filename_color'], 240, attr=Powerline.ATTR_BOLD, draw_divider=not len(windata['modified'])),
|
||||||
Segment(windata['modified'], 220, 240, attr=Segment.ATTR_BOLD),
|
mksegment(windata['modified'], 220, 240, attr=Powerline.ATTR_BOLD),
|
||||||
Segment(windata['currenttag'], 246, 236, draw_divider=False, priority=100),
|
mksegment(windata['currenttag'], 246, 236, draw_divider=False, priority=100),
|
||||||
Segment(filler=True, fg=236, bg=236),
|
mksegment(filler=True, cterm_fg=236, cterm_bg=236),
|
||||||
Segment(windata['fileformat'], 247, 236, side='r', priority=50),
|
mksegment(windata['fileformat'], 247, 236, side='r', priority=50),
|
||||||
Segment(windata['fileencoding'], 247, 236, side='r', priority=50),
|
mksegment(windata['fileencoding'], 247, 236, side='r', priority=50),
|
||||||
Segment(windata['filetype'], 247, 236, side='r', priority=50),
|
mksegment(windata['filetype'], 247, 236, side='r', priority=50),
|
||||||
Segment(windata['line_percent'], windata['line_percent_color'], 240, side='r', priority=30),
|
mksegment(windata['line_percent'], windata['line_percent_color'], 240, side='r', priority=30),
|
||||||
Segment('⭡ ', 239, 252, side='r'),
|
mksegment(u'⭡ ', 239, 252, side='r'),
|
||||||
Segment(windata['linecurrent'], 235, 252, attr=Segment.ATTR_BOLD, side='r', draw_divider=False),
|
mksegment(windata['linecurrent'], 235, 252, attr=Powerline.ATTR_BOLD, side='r', draw_divider=False),
|
||||||
Segment(windata['colcurrent'], 244, 252, side='r', priority=30, draw_divider=False),
|
mksegment(windata['colcurrent'], 244, 252, side='r', priority=30, draw_divider=False),
|
||||||
])
|
])
|
||||||
|
|
||||||
renderer = VimSegmentRenderer()
|
renderer = VimSegmentRenderer()
|
||||||
stl = powerline.render(renderer, winwidth)
|
stl = powerline.render(renderer, winwidth)
|
||||||
|
|
||||||
# Escape percent chars in the statusline, but only if they aren't part of any stl escape sequence
|
# Replace percent placeholders
|
||||||
stl = re.sub('(\w+)\%(?![-{()<=#*%])', '\\1%%', stl)
|
stl = stl.replace(percent_placeholder, '%%')
|
||||||
|
|
||||||
# Create highlighting groups
|
# Create highlighting groups
|
||||||
for idx, hl in renderer.hl_groups.items():
|
for idx, hl in renderer.hl_groups.items():
|
||||||
|
121
lib/core.py
121
lib/core.py
@ -3,15 +3,19 @@
|
|||||||
from lib.colors import cterm_to_hex
|
from lib.colors import cterm_to_hex
|
||||||
|
|
||||||
|
|
||||||
class Powerline:
|
class Powerline(object):
|
||||||
|
ATTR_BOLD = 1
|
||||||
|
ATTR_ITALIC = 2
|
||||||
|
ATTR_UNDERLINE = 4
|
||||||
|
|
||||||
dividers = {
|
dividers = {
|
||||||
'l': {
|
'l': {
|
||||||
'hard': '⮀',
|
'hard': u'⮀',
|
||||||
'soft': '⮁',
|
'soft': u'⮁',
|
||||||
},
|
},
|
||||||
'r': {
|
'r': {
|
||||||
'hard': '⮂',
|
'hard': u'⮂',
|
||||||
'soft': '⮃',
|
'soft': u'⮃',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +25,7 @@ class Powerline:
|
|||||||
Segments that have empty contents and aren't filler segments are
|
Segments that have empty contents and aren't filler segments are
|
||||||
dropped from the segment array.
|
dropped from the segment array.
|
||||||
'''
|
'''
|
||||||
self.segments = [segment for segment in segments if segment.contents or segment.filler]
|
self.segments = [segment for segment in segments if segment['contents'] or segment['filler']]
|
||||||
self._hl = {}
|
self._hl = {}
|
||||||
|
|
||||||
def render(self, renderer, width=None):
|
def render(self, renderer, width=None):
|
||||||
@ -45,60 +49,60 @@ class Powerline:
|
|||||||
rendering is used for calculating the total width for dropping
|
rendering is used for calculating the total width for dropping
|
||||||
low-priority segments.
|
low-priority segments.
|
||||||
'''
|
'''
|
||||||
rendered_highlighted = ''
|
rendered_highlighted = u''
|
||||||
segments_len = len(segments)
|
segments_len = len(segments)
|
||||||
empty_segment = Segment()
|
empty_segment = mksegment()
|
||||||
|
|
||||||
for idx, segment in enumerate(segments):
|
for idx, segment in enumerate(segments):
|
||||||
prev = segments[idx - 1] if idx > 0 else empty_segment
|
prev = segments[idx - 1] if idx > 0 else empty_segment
|
||||||
next = segments[idx + 1] if idx < segments_len - 1 else empty_segment
|
next = segments[idx + 1] if idx < segments_len - 1 else empty_segment
|
||||||
|
|
||||||
compare = next if segment.side == 'l' else prev
|
compare = next if segment['side'] == 'l' else prev
|
||||||
outer_padding = ' ' if idx == 0 or idx == segments_len - 1 else ''
|
outer_padding = ' ' if idx == 0 or idx == segments_len - 1 else ''
|
||||||
divider_type = 'soft' if compare.bg == segment.bg else 'hard'
|
divider_type = 'soft' if compare['bg'] == segment['bg'] else 'hard'
|
||||||
divider = self.dividers[segment.side][divider_type]
|
divider = self.dividers[segment['side']][divider_type]
|
||||||
divider_hl = ''
|
divider_hl = ''
|
||||||
segment_hl = ''
|
segment_hl = ''
|
||||||
|
|
||||||
if render_highlighted:
|
if render_highlighted:
|
||||||
# Generate and cache renderer highlighting
|
# Generate and cache renderer highlighting
|
||||||
if divider_type == 'hard':
|
if divider_type == 'hard':
|
||||||
hl_key = (segment.bg, compare.bg)
|
hl_key = (segment['bg'], compare['bg'])
|
||||||
if not hl_key in self._hl:
|
if not hl_key in self._hl:
|
||||||
self._hl[hl_key] = renderer.hl(*hl_key)
|
self._hl[hl_key] = renderer.hl(*hl_key)
|
||||||
divider_hl = self._hl[hl_key]
|
divider_hl = self._hl[hl_key]
|
||||||
|
|
||||||
hl_key = (segment.fg, segment.bg, segment.attr)
|
hl_key = (segment['fg'], segment['bg'], segment['attr'])
|
||||||
if not hl_key in self._hl:
|
if not hl_key in self._hl:
|
||||||
self._hl[hl_key] = renderer.hl(*hl_key)
|
self._hl[hl_key] = renderer.hl(*hl_key)
|
||||||
segment_hl = self._hl[hl_key]
|
segment_hl = self._hl[hl_key]
|
||||||
|
|
||||||
if segment.filler:
|
if segment['filler']:
|
||||||
# Filler segments shouldn't be padded
|
# Filler segments shouldn't be padded
|
||||||
rendered_highlighted += segment.contents
|
rendered_highlighted += segment['contents']
|
||||||
elif segment.draw_divider and (divider_type == 'hard' or segment.side == compare.side):
|
elif segment['draw_divider'] and (divider_type == 'hard' or segment['side'] == compare['side']):
|
||||||
# Draw divider if specified, and if the next segment is on
|
# Draw divider if specified, and if the next segment is on
|
||||||
# the opposite side only draw the divider if it's a hard
|
# the opposite side only draw the divider if it's a hard
|
||||||
# divider
|
# divider
|
||||||
if segment.side == 'l':
|
if segment['side'] == 'l':
|
||||||
segment.rendered_raw += outer_padding + segment.contents + ' ' + divider + ' '
|
segment['rendered_raw'] += outer_padding + segment['contents'] + ' ' + divider + ' '
|
||||||
rendered_highlighted += segment_hl + outer_padding + segment.contents + ' ' + divider_hl + divider + ' '
|
rendered_highlighted += segment_hl + outer_padding + segment['contents'] + ' ' + divider_hl + divider + ' '
|
||||||
else:
|
else:
|
||||||
segment.rendered_raw += ' ' + divider + ' ' + segment.contents + outer_padding
|
segment['rendered_raw'] += ' ' + divider + ' ' + segment['contents'] + outer_padding
|
||||||
rendered_highlighted += ' ' + divider_hl + divider + segment_hl + ' ' + segment.contents + outer_padding
|
rendered_highlighted += ' ' + divider_hl + divider + segment_hl + ' ' + segment['contents'] + outer_padding
|
||||||
elif segment.contents:
|
elif segment['contents']:
|
||||||
# Segments without divider
|
# Segments without divider
|
||||||
if segment.side == 'l':
|
if segment['side'] == 'l':
|
||||||
segment.rendered_raw += outer_padding + segment.contents
|
segment['rendered_raw'] += outer_padding + segment['contents']
|
||||||
rendered_highlighted += segment_hl + outer_padding + segment.contents
|
rendered_highlighted += segment_hl + outer_padding + segment['contents']
|
||||||
else:
|
else:
|
||||||
segment.rendered_raw += segment.contents + outer_padding
|
segment['rendered_raw'] += segment['contents'] + outer_padding
|
||||||
rendered_highlighted += segment_hl + segment.contents + outer_padding
|
rendered_highlighted += segment_hl + segment['contents'] + outer_padding
|
||||||
else:
|
else:
|
||||||
# Unknown segment type, skip it
|
# Unknown segment type, skip it
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return rendered_highlighted.decode('utf-8')
|
return rendered_highlighted
|
||||||
|
|
||||||
rendered_highlighted = render_segments(self.segments)
|
rendered_highlighted = render_segments(self.segments)
|
||||||
|
|
||||||
@ -107,22 +111,21 @@ class Powerline:
|
|||||||
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(self.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() > width and len(segments_priority):
|
||||||
# FIXME The remove method is quite expensive and we should find another way of removing low-priority segments
|
|
||||||
self.segments.remove(segments_priority[0])
|
self.segments.remove(segments_priority[0])
|
||||||
segments_priority.pop(0)
|
segments_priority.pop(0)
|
||||||
|
|
||||||
# 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.filler is True]
|
segments_fillers = [segment for segment in self.segments if segment['filler'] is True]
|
||||||
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()), 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 render_segments(self.segments)
|
return render_segments(self.segments)
|
||||||
|
|
||||||
@ -132,39 +135,25 @@ class Powerline:
|
|||||||
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]).decode('utf-8'))
|
return len(''.join([segment['rendered_raw'] for segment in self.segments]))
|
||||||
|
|
||||||
|
|
||||||
class Segment:
|
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):
|
||||||
ATTR_BOLD = 1
|
'''Convenience wrapper for segment generation.
|
||||||
ATTR_ITALIC = 2
|
'''
|
||||||
ATTR_UNDERLINE = 4
|
try:
|
||||||
|
contents = unicode(contents or u'')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
contents = contents.decode('utf-8') or u''
|
||||||
|
|
||||||
def __init__(self, contents=None, fg=False, bg=False, attr=False, side='l', draw_divider=True, priority=-1, filler=False):
|
return {
|
||||||
'''Create a new Powerline segment.
|
'contents': contents,
|
||||||
'''
|
'fg': (cterm_fg, hex_fg or cterm_to_hex.get(cterm_fg, 0xffffff)),
|
||||||
self.contents = str(contents or '')
|
'bg': (cterm_bg, hex_bg or cterm_to_hex.get(cterm_bg, 0x000000)),
|
||||||
self.fg = fg
|
'attr': attr,
|
||||||
self.bg = bg
|
'side': side,
|
||||||
self.attr = attr
|
'draw_divider': False if filler else draw_divider,
|
||||||
self.side = side
|
'priority': priority,
|
||||||
self.draw_divider = draw_divider
|
'filler': filler,
|
||||||
self.priority = priority
|
'rendered_raw': u'',
|
||||||
self.filler = filler
|
}
|
||||||
self.rendered_raw = ''
|
|
||||||
|
|
||||||
if self.filler:
|
|
||||||
# Filler segments should never have any dividers
|
|
||||||
self.draw_divider = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.fg = (fg[0], fg[1])
|
|
||||||
except TypeError:
|
|
||||||
# Only the terminal color is defined, so we need to get the hex color
|
|
||||||
self.fg = (self.fg, cterm_to_hex.get(self.fg, 0xffffff))
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.bg = (bg[0], bg[1])
|
|
||||||
except TypeError:
|
|
||||||
# Only the terminal color is defined, so we need to get the hex color
|
|
||||||
self.bg = (self.bg, cterm_to_hex.get(self.bg, 0x000000))
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
from lib.core import Segment
|
from lib.core import Powerline
|
||||||
from lib.renderers import SegmentRenderer
|
from lib.renderers import SegmentRenderer
|
||||||
|
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class TerminalSegmentRenderer(SegmentRenderer):
|
|||||||
if attr is False:
|
if attr is False:
|
||||||
ansi += [22]
|
ansi += [22]
|
||||||
else:
|
else:
|
||||||
if attr & Segment.ATTR_BOLD:
|
if attr & Powerline.ATTR_BOLD:
|
||||||
ansi += [1]
|
ansi += [1]
|
||||||
|
|
||||||
return '[{0}m'.format(';'.join(str(attr) for attr in ansi))
|
return '[{0}m'.format(';'.join(str(attr) for attr in ansi))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
from lib.core import Segment
|
from lib.core import Powerline
|
||||||
from lib.renderers import SegmentRenderer
|
from lib.renderers import SegmentRenderer
|
||||||
|
|
||||||
|
|
||||||
@ -41,11 +41,11 @@ class VimSegmentRenderer(SegmentRenderer):
|
|||||||
|
|
||||||
if attr:
|
if attr:
|
||||||
hl_group['attr'] = []
|
hl_group['attr'] = []
|
||||||
if attr & Segment.ATTR_BOLD:
|
if attr & Powerline.ATTR_BOLD:
|
||||||
hl_group['attr'].append('bold')
|
hl_group['attr'].append('bold')
|
||||||
if attr & Segment.ATTR_ITALIC:
|
if attr & Powerline.ATTR_ITALIC:
|
||||||
hl_group['attr'].append('italic')
|
hl_group['attr'].append('italic')
|
||||||
if attr & Segment.ATTR_UNDERLINE:
|
if attr & Powerline.ATTR_UNDERLINE:
|
||||||
hl_group['attr'].append('underline')
|
hl_group['attr'].append('underline')
|
||||||
|
|
||||||
hl_group['name'] = 'Pl_' + \
|
hl_group['name'] = 'Pl_' + \
|
||||||
|
Loading…
x
Reference in New Issue
Block a user