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 return r
vim_get_func = VimFunc vim_get_func = VimFunc
getbufvar = vim_get_func('getbufvar')

View File

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

View File

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

View File

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

View File

@ -2,12 +2,14 @@
from __future__ import absolute_import from __future__ import absolute_import
import vim import os
from powerline.bindings.vim import getbufvar
def help(): def help(matcher_info):
return bool(int(vim.eval('&buftype is# "help"'))) return getbufvar(matcher_info['bufnr'], '&buftype') == 'help'
def cmdwin(): def cmdwin(matcher_info):
return bool(int(vim.eval('bufname("%") is# "[Command Line]"'))) 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') raise KeyError('There is already a local theme with given matcher')
self.local_themes[matcher] = theme self.local_themes[matcher] = theme
def get_theme(self): def get_theme(self, matcher_info):
for matcher in self.local_themes.keys(): for matcher in self.local_themes.keys():
if matcher(): if matcher(matcher_info):
match = self.local_themes[matcher] match = self.local_themes[matcher]
if 'config' in match: if 'config' in match:
match['theme'] = Theme(theme_config=match.pop('config'), **self.theme_kwargs) match['theme'] = Theme(theme_config=match.pop('config'), **self.theme_kwargs)
@ -26,7 +26,7 @@ class Renderer(object):
else: else:
return self.theme 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. '''Render all segments.
When a width is provided, low-priority segments are dropped one at 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 provided they will fill the remaining space until the desired width is
reached. reached.
''' '''
theme = theme or self.get_theme() theme = self.get_theme(matcher_info)
segments = segments or theme.get_segments(side) segments = theme.get_segments(side)
if segment_info:
theme.segment_info.update(segment_info)
# Handle excluded/included segments for the current mode # Handle excluded/included segments for the current mode
segments = [segment for segment in segments\ segments = [segment for segment in segments\

View File

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

View File

@ -8,17 +8,16 @@ try:
except ImportError: except ImportError:
vim = {} 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 import memoize, humanize_bytes
from powerline.lib.vcs import guess from powerline.lib.vcs import guess
vim_funcs = { vim_funcs = {
'col': vim_get_func('col', rettype=int),
'virtcol': vim_get_func('virtcol', rettype=int), 'virtcol': vim_get_func('virtcol', rettype=int),
'expand': vim_get_func('expand'), 'fnamemodify': vim_get_func('fnamemodify'),
'line': vim_get_func('line', rettype=int),
'mode': vim_get_func('mode'),
'getfsize': vim_get_func('getfsize', rettype=int), 'getfsize': vim_get_func('getfsize', rettype=int),
'bufnr': vim_get_func('bufnr', rettype=int),
} }
vim_modes = { 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. '''Return the current vim mode.
:param dict override: :param dict override:
dict for overriding default mode strings, e.g. ``{ 'n': 'NORM' }`` 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) mode = mode_translations.get(mode, mode)
if not override: if not override:
return vim_modes[mode] return vim_modes[mode]
@ -64,48 +88,54 @@ def mode(override=None):
return vim_modes[mode] return vim_modes[mode]
def modified_indicator(text=u'+'): @requires_segment_info
def modified_indicator(segment_info, text=u'+'):
'''Return a file modified indicator. '''Return a file modified indicator.
:param string text: :param string text:
text to display if the current buffer is modified 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. '''Return a paste mode indicator.
:param string text: :param string text:
text to display if paste mode is enabled 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. '''Return a read-only indicator.
:param string text: :param string text:
text to display if the current buffer is read-only 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). '''Return file directory (head component of the file path).
:param bool shorten_home: :param bool shorten_home:
shorten all directories in :file:`/home/` to :file:`~user/` instead of :file:`/home/user/`. shorten all directories in :file:`/home/` to :file:`~user/` instead of :file:`/home/user/`.
''' '''
file_directory = vim_funcs['expand']('%:~:.:h') name = segment_info['buffer'].name
if file_directory is None: if not name:
return None return None
file_directory = vim_funcs['fnamemodify'](name, ':~:.:h')
if shorten_home and file_directory.startswith('/home/'): if shorten_home and file_directory.startswith('/home/'):
file_directory = '~' + file_directory[6:] file_directory = '~' + file_directory[6:]
return file_directory.decode('utf-8') + os.sep if file_directory else None 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). '''Return file name (tail component of the file path).
:param bool display_no_file: :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: :param str no_file_text:
the string to display if the buffer is missing a file name the string to display if the buffer is missing a file name
''' '''
file_name = vim_funcs['expand']('%:~:.:t') name = segment_info['buffer'].name
if not file_name and not display_no_file: if not name:
return None if display_no_file:
if not file_name: return [{
return [{ 'contents': no_file_text,
'contents': no_file_text, 'highlight_group': ['file_name_no_file', 'file_name'],
'highlight_group': ['file_name_no_file', 'file_name'], }]
}] else:
return None
file_name = vim_funcs['fnamemodify'](name, ':~:.:t')
return file_name.decode('utf-8') return file_name.decode('utf-8')
@memoize(2) @requires_segment_info
def file_size(suffix='B', binary_prefix=False): @memoize(2, additional_key=bufnr)
def file_size(segment_info, suffix='B', binary_prefix=False):
'''Return file size. '''Return file size.
:param str suffix: :param str suffix:
@ -134,45 +167,49 @@ def file_size(suffix='B', binary_prefix=False):
use binary prefix, e.g. MiB instead of MB 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 :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) file_size = vim_funcs['getfsize'](file_name)
if file_size < 0: if file_size < 0:
return None return None
return humanize_bytes(file_size, suffix, binary_prefix) 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 (i.e. line ending type).
:return: file format or None if unknown or missing file format :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.
:return: file encoding/character set or None if unknown or missing file encoding :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.
:return: file type or None if unknown 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. '''Return the cursor position in the file as a percentage.
:param bool gradient: :param bool gradient:
highlight the percentage with a color gradient (by default a green to red gradient) highlight the percentage with a color gradient (by default a green to red gradient)
''' '''
line_current = vim_funcs['line']('.') line_current = segment_info['window'].cursor[0]
line_last = vim_funcs['line']('$') line_last = len(segment_info['buffer'])
percentage = int(line_current * 100 // line_last) percentage = int(line_current * 100 // line_last)
if not gradient: if not gradient:
return percentage 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 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. '''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=','): def modified_buffers(text=u'+', join_str=','):
@ -204,30 +246,33 @@ def modified_buffers(text=u'+', join_str=','):
:param str join_str: :param str join_str:
string to use for joining the modified buffer list string to use for joining the modified buffer list
''' '''
buffer_len = int(vim.eval('bufnr("$")')) buffer_len = vim_funcs['bufnr']('$')
buffer_mod = [str(bufnr) for bufnr in range(1, buffer_len + 1) if vim.eval('getbufvar({0}, "&mod")'.format(bufnr)) == '1'] buffer_mod = [str(bufnr) for bufnr in range(1, buffer_len + 1) if int(getbufvar(bufnr, '&modified'))]
if buffer_mod: if buffer_mod:
return u'{0} {1}'.format(text, join_str.join(buffer_mod)) return u'{0} {1}'.format(text, join_str.join(buffer_mod))
return None return None
@requires_segment_info
@memoize(2) @memoize(2)
def branch(): def branch(segment_info):
'''Return the current working branch.''' '''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: if repo:
return repo.branch() return repo.branch()
return None return None
# TODO Drop cache on BufWrite event # TODO Drop cache on BufWrite event
@memoize(2, additional_key=lambda: vim.current.buffer.number) @requires_segment_info
def file_vcs_status(): @memoize(2, additional_key=bufnr)
def file_vcs_status(segment_info):
'''Return the VCS status for this buffer.''' '''Return the VCS status for this buffer.'''
if vim.current.buffer.name and not vim.eval('&buftype'): name = segment_info['buffer'].name
repo = guess(os.path.abspath(vim.current.buffer.name)) if name and not getbufvar(segment_info['bufnr'], '&buftype'):
repo = guess(os.path.abspath(name))
if repo: 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: if not status:
return None return None
status = status.strip() status = status.strip()
@ -242,10 +287,11 @@ def file_vcs_status():
return None return None
@requires_segment_info
@memoize(2) @memoize(2)
def repository_status(): def repository_status(segment_info):
'''Return the status for the current repo.''' '''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: if repo:
return repo.status() return repo.status()
return None return None

View File

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