Remove non-current window caching

API changes done:
- memoize additional_key function now accepts all function arguments
- get_theme now receives matcher_info
- render now receives segment_info and matcher_info, but segments and themes
  were removed
- due to very different ways of obtaining column information col_current
  splitted back to col_current and virtcol_current. The former should be false
  in case of horizontal scrollbind (when &scrollopt contains hor)
- added requires_segment_info decorator for convenience

Other changes:
- removed all vim function calls that were possible to remove
- removed direct vim.eval calls
This commit is contained in:
ZyX 2013-02-11 22:10:09 +04:00 committed by Kim Silkebækken
parent cc83d741ff
commit d638f1d6ea
9 changed files with 137 additions and 79 deletions

View File

@ -46,3 +46,5 @@ except AttributeError:
return r
vim_get_func = VimFunc
getbufvar = vim_get_func('getbufvar')

View File

@ -36,7 +36,7 @@ catch
finish
endtry
endtry
exec s:powerline_pycmd 'powerline = Powerline("vim")'
exec s:powerline_pycmd 'powerline = Powerline("vim", segment_info={})'
if exists('*'. s:powerline_pyeval)
let s:pyeval = function(s:powerline_pyeval)

View File

@ -87,11 +87,12 @@
"align": "r"
},
{
"name": "col_current",
"name": "virtcol_current",
"draw_divider": false,
"priority": 30,
"before": ":",
"width": 3,
"highlight_group": ["col_current"],
"align": "l"
}
]

View File

@ -16,7 +16,7 @@ class memoize(object):
@wraps(func)
def decorated_function(*args, **kwargs):
if self.additional_key:
key = (func.__name__, args, tuple(kwargs.items()), self.additional_key())
key = (func.__name__, args, tuple(kwargs.items()), self.additional_key(*args, **kwargs))
else:
key = (func.__name__, args, tuple(kwargs.items()))
cached = self._cache.get(key, None)

View File

@ -2,12 +2,14 @@
from __future__ import absolute_import
import vim
import os
from powerline.bindings.vim import getbufvar
def help():
return bool(int(vim.eval('&buftype is# "help"')))
def help(matcher_info):
return getbufvar(matcher_info['bufnr'], '&buftype') == 'help'
def cmdwin():
return bool(int(vim.eval('bufname("%") is# "[Command Line]"')))
def cmdwin(matcher_info):
name = matcher_info['buffer'].name
return name and os.path.basename(name) == '[Command Line]'

View File

@ -16,9 +16,9 @@ class Renderer(object):
raise KeyError('There is already a local theme with given matcher')
self.local_themes[matcher] = theme
def get_theme(self):
def get_theme(self, matcher_info):
for matcher in self.local_themes.keys():
if matcher():
if matcher(matcher_info):
match = self.local_themes[matcher]
if 'config' in match:
match['theme'] = Theme(theme_config=match.pop('config'), **self.theme_kwargs)
@ -26,7 +26,7 @@ class Renderer(object):
else:
return self.theme
def render(self, mode=None, width=None, theme=None, segments=None, side=None, output_raw=False):
def render(self, mode=None, width=None, side=None, output_raw=False, segment_info=None, matcher_info=None):
'''Render all segments.
When a width is provided, low-priority segments are dropped one at
@ -35,8 +35,11 @@ class Renderer(object):
provided they will fill the remaining space until the desired width is
reached.
'''
theme = theme or self.get_theme()
segments = segments or theme.get_segments(side)
theme = self.get_theme(matcher_info)
segments = theme.get_segments(side)
if segment_info:
theme.segment_info.update(segment_info)
# Handle excluded/included segments for the current mode
segments = [segment for segment in segments\

View File

@ -9,7 +9,6 @@ from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
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')
@ -20,7 +19,6 @@ class VimRenderer(Renderer):
def __init__(self, *args, **kwargs):
super(VimRenderer, self).__init__(*args, **kwargs)
self.hl_groups = {}
self.window_cache = {}
def render(self, winnr, current):
'''Render all segments.
@ -30,19 +28,20 @@ class VimRenderer(Renderer):
used in non-current windows.
'''
window_id = vim_getwinvar(winnr, 'window_id')
winwidth = vim_winwidth(winnr)
if current:
mode = vim_mode()
theme = self.get_theme()
segments = [segment for segment in theme.get_segments()]
self.window_cache[window_id] = (theme, segments)
else:
mode = 'nc'
theme, segments = self.window_cache.get(window_id, (None, []))
for segment in segments:
segment['_space_left'] = 0
segment['_space_right'] = 0
statusline = super(VimRenderer, self).render(mode, winwidth, theme, segments)
segment_info = {
'window': vim.windows[winnr - 1],
'winnr': winnr,
'mode': mode,
'window_id': window_id,
}
segment_info['buffer'] = segment_info['window'].buffer
segment_info['bufnr'] = segment_info['buffer'].number
winwidth = segment_info['window'].width
statusline = super(VimRenderer, self).render(mode, winwidth, segment_info=segment_info, matcher_info=segment_info)
return statusline
def reset_highlight(self):

View File

@ -8,17 +8,16 @@ try:
except ImportError:
vim = {}
from powerline.bindings.vim import vim_get_func
from powerline.bindings.vim import vim_get_func, getbufvar
from powerline.theme import requires_segment_info
from powerline.lib import memoize, humanize_bytes
from powerline.lib.vcs import guess
vim_funcs = {
'col': vim_get_func('col', rettype=int),
'virtcol': vim_get_func('virtcol', rettype=int),
'expand': vim_get_func('expand'),
'line': vim_get_func('line', rettype=int),
'mode': vim_get_func('mode'),
'fnamemodify': vim_get_func('fnamemodify'),
'getfsize': vim_get_func('getfsize', rettype=int),
'bufnr': vim_get_func('bufnr', rettype=int),
}
vim_modes = {
@ -48,13 +47,38 @@ mode_translations = {
}
def mode(override=None):
def bufnr(segment_info, *args, **kwargs):
'''Used for cache key, returns current buffer number'''
return segment_info['bufnr']
# TODO Remove cache when needed
def window_cached(func):
cache = {}
def ret(segment_info, *args, **kwargs):
window_id = segment_info['window_id']
if segment_info['mode'] == 'nc':
return cache.get(window_id)
else:
r = func(*args, **kwargs)
cache[window_id] = r
return r
ret = requires_segment_info(ret)
ret.__name__ = func.__name__
return ret
@requires_segment_info
def mode(segment_info, override=None):
'''Return the current vim mode.
:param dict override:
dict for overriding default mode strings, e.g. ``{ 'n': 'NORM' }``
'''
mode = vim_funcs['mode']().decode('utf-8')
mode = segment_info['mode']
if mode == 'nc':
return None
mode = mode_translations.get(mode, mode)
if not override:
return vim_modes[mode]
@ -64,48 +88,54 @@ def mode(override=None):
return vim_modes[mode]
def modified_indicator(text=u'+'):
@requires_segment_info
def modified_indicator(segment_info, text=u'+'):
'''Return a file modified indicator.
:param string text:
text to display if the current buffer is modified
'''
return text if int(vim.eval('&modified')) else None
return text if int(getbufvar(segment_info['bufnr'], '&modified')) else None
def paste_indicator(text='PASTE'):
@requires_segment_info
def paste_indicator(segment_info, text='PASTE'):
'''Return a paste mode indicator.
:param string text:
text to display if paste mode is enabled
'''
return text if int(vim.eval('&paste')) else None
return text if int(getbufvar(segment_info['bufnr'], '&paste')) else None
def readonly_indicator(text=u''):
@requires_segment_info
def readonly_indicator(segment_info, text=u''):
'''Return a read-only indicator.
:param string text:
text to display if the current buffer is read-only
'''
return text if int(vim.eval('&readonly')) else None
return text if int(getbufvar(segment_info['bufnr'], '&readonly')) else None
def file_directory(shorten_home=False):
@requires_segment_info
def file_directory(segment_info, shorten_home=False):
'''Return file directory (head component of the file path).
:param bool shorten_home:
shorten all directories in :file:`/home/` to :file:`~user/` instead of :file:`/home/user/`.
'''
file_directory = vim_funcs['expand']('%:~:.:h')
if file_directory is None:
name = segment_info['buffer'].name
if not name:
return None
file_directory = vim_funcs['fnamemodify'](name, ':~:.:h')
if shorten_home and file_directory.startswith('/home/'):
file_directory = '~' + file_directory[6:]
return file_directory.decode('utf-8') + os.sep if file_directory else None
def file_name(display_no_file=False, no_file_text='[No file]'):
@requires_segment_info
def file_name(segment_info, display_no_file=False, no_file_text='[No file]'):
'''Return file name (tail component of the file path).
:param bool display_no_file:
@ -113,19 +143,22 @@ def file_name(display_no_file=False, no_file_text='[No file]'):
:param str no_file_text:
the string to display if the buffer is missing a file name
'''
file_name = vim_funcs['expand']('%:~:.:t')
if not file_name and not display_no_file:
return None
if not file_name:
return [{
'contents': no_file_text,
'highlight_group': ['file_name_no_file', 'file_name'],
}]
name = segment_info['buffer'].name
if not name:
if display_no_file:
return [{
'contents': no_file_text,
'highlight_group': ['file_name_no_file', 'file_name'],
}]
else:
return None
file_name = vim_funcs['fnamemodify'](name, ':~:.:t')
return file_name.decode('utf-8')
@memoize(2)
def file_size(suffix='B', binary_prefix=False):
@requires_segment_info
@memoize(2, additional_key=bufnr)
def file_size(segment_info, suffix='B', binary_prefix=False):
'''Return file size.
:param str suffix:
@ -134,45 +167,49 @@ def file_size(suffix='B', binary_prefix=False):
use binary prefix, e.g. MiB instead of MB
:return: file size or None if the file isn't saved or if the size is too big to fit in a number
'''
file_name = vim_funcs['expand']('%')
file_name = segment_info['buffer'].name
file_size = vim_funcs['getfsize'](file_name)
if file_size < 0:
return None
return humanize_bytes(file_size, suffix, binary_prefix)
def file_format():
@requires_segment_info
def file_format(segment_info):
'''Return file format (i.e. line ending type).
:return: file format or None if unknown or missing file format
'''
return vim.eval('&fileformat') or None
return getbufvar(segment_info['bufnr'], '&fileformat') or None
def file_encoding():
@requires_segment_info
def file_encoding(segment_info):
'''Return file encoding/character set.
:return: file encoding/character set or None if unknown or missing file encoding
'''
return vim.eval('&fileencoding') or None
return getbufvar(segment_info['bufnr'], '&fileencoding') or None
def file_type():
@requires_segment_info
def file_type(segment_info):
'''Return file type.
:return: file type or None if unknown file type
'''
return vim.eval('&filetype') or None
return getbufvar(segment_info['bufnr'], '&filetype') or None
def line_percent(gradient=False):
@requires_segment_info
def line_percent(segment_info, gradient=False):
'''Return the cursor position in the file as a percentage.
:param bool gradient:
highlight the percentage with a color gradient (by default a green to red gradient)
'''
line_current = vim_funcs['line']('.')
line_last = vim_funcs['line']('$')
line_current = segment_info['window'].cursor[0]
line_last = len(segment_info['buffer'])
percentage = int(line_current * 100 // line_last)
if not gradient:
return percentage
@ -182,18 +219,23 @@ def line_percent(gradient=False):
}]
def line_current():
@requires_segment_info
def line_current(segment_info):
'''Return the current cursor line.'''
return vim_funcs['line']('.')
return segment_info['window'].cursor[0]
def col_current(virtcol=True):
@requires_segment_info
def col_current(segment_info):
'''Return the current cursor column.
:param bool virtcol:
return visual column with concealed characters ingored
'''
return vim_funcs['virtcol' if virtcol else 'col']('.')
return segment_info['window'].cursor[1] + 1
@window_cached
def virtcol_current():
'''Return current visual column with concealed characters ingored'''
return vim_funcs['virtcol']('.')
def modified_buffers(text=u'+', join_str=','):
@ -204,30 +246,33 @@ def modified_buffers(text=u'+', join_str=','):
:param str join_str:
string to use for joining the modified buffer list
'''
buffer_len = int(vim.eval('bufnr("$")'))
buffer_mod = [str(bufnr) for bufnr in range(1, buffer_len + 1) if vim.eval('getbufvar({0}, "&mod")'.format(bufnr)) == '1']
buffer_len = vim_funcs['bufnr']('$')
buffer_mod = [str(bufnr) for bufnr in range(1, buffer_len + 1) if int(getbufvar(bufnr, '&modified'))]
if buffer_mod:
return u'{0} {1}'.format(text, join_str.join(buffer_mod))
return None
@requires_segment_info
@memoize(2)
def branch():
def branch(segment_info):
'''Return the current working branch.'''
repo = guess(os.path.abspath(vim.current.buffer.name or os.getcwd()))
repo = guess(os.path.abspath(segment_info['buffer'].name or os.getcwd()))
if repo:
return repo.branch()
return None
# TODO Drop cache on BufWrite event
@memoize(2, additional_key=lambda: vim.current.buffer.number)
def file_vcs_status():
@requires_segment_info
@memoize(2, additional_key=bufnr)
def file_vcs_status(segment_info):
'''Return the VCS status for this buffer.'''
if vim.current.buffer.name and not vim.eval('&buftype'):
repo = guess(os.path.abspath(vim.current.buffer.name))
name = segment_info['buffer'].name
if name and not getbufvar(segment_info['bufnr'], '&buftype'):
repo = guess(os.path.abspath(name))
if repo:
status = repo.status(os.path.relpath(vim.current.buffer.name, repo.directory))
status = repo.status(os.path.relpath(name, repo.directory))
if not status:
return None
status = status.strip()
@ -242,10 +287,11 @@ def file_vcs_status():
return None
@requires_segment_info
@memoize(2)
def repository_status():
def repository_status(segment_info):
'''Return the status for the current repo.'''
repo = guess(os.path.abspath(vim.current.buffer.name or os.getcwd()))
repo = guess(os.path.abspath(segment_info['buffer'].name or os.getcwd()))
if repo:
return repo.status()
return None

View File

@ -11,6 +11,11 @@ except NameError:
unicode = str
def requires_segment_info(func):
func.requires_powerline_segment_info = True
return func
class Theme(object):
def __init__(self, ext, colorscheme, theme_config, common_config, segment_info=None):
self.colorscheme = colorscheme