summaryrefslogtreecommitdiffstats
path: root/powerline/vim.py
diff options
context:
space:
mode:
Diffstat (limited to 'powerline/vim.py')
-rw-r--r--powerline/vim.py359
1 files changed, 359 insertions, 0 deletions
diff --git a/powerline/vim.py b/powerline/vim.py
new file mode 100644
index 0000000..2638c91
--- /dev/null
+++ b/powerline/vim.py
@@ -0,0 +1,359 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import json
+import logging
+
+from itertools import count
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import vim_get_func, vim_getvar, get_vim_encoding, python_to_vim
+from powerline import Powerline, FailedUnicode, finish_common_config
+from powerline.lib.dict import mergedicts
+from powerline.lib.unicode import u
+
+
+def _override_from(config, override_varname, key=None):
+ try:
+ overrides = vim_getvar(override_varname)
+ except KeyError:
+ return config
+ if key is not None:
+ try:
+ overrides = overrides[key]
+ except KeyError:
+ return config
+ mergedicts(config, overrides)
+ return config
+
+
+class VimVarHandler(logging.Handler, object):
+ '''Vim-specific handler which emits messages to Vim global variables
+
+ :param str varname:
+ Variable where
+ '''
+ def __init__(self, varname):
+ super(VimVarHandler, self).__init__()
+ utf_varname = u(varname)
+ self.vim_varname = utf_varname.encode('ascii')
+ vim.command('unlet! g:' + utf_varname)
+ vim.command('let g:' + utf_varname + ' = []')
+
+ def emit(self, record):
+ message = u(record.message)
+ if record.exc_text:
+ message += '\n' + u(record.exc_text)
+ vim.eval(b'add(g:' + self.vim_varname + b', ' + python_to_vim(message) + b')')
+
+
+class VimPowerline(Powerline):
+ def init(self, pyeval='PowerlinePyeval', **kwargs):
+ super(VimPowerline, self).init('vim', **kwargs)
+ self.last_window_id = 1
+ self.pyeval = pyeval
+ self.construct_window_statusline = self.create_window_statusline_constructor()
+ if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))):
+ self.win_idx = self.new_win_idx
+ else:
+ self.win_idx = self.old_win_idx
+ self._vim_getwinvar = vim_get_func('getwinvar', 'bytes')
+ self._vim_setwinvar = vim_get_func('setwinvar')
+
+ if sys.version_info < (3,):
+ def create_window_statusline_constructor(self):
+ window_statusline = b'%!' + str(self.pyeval) + b'(\'powerline.statusline({0})\')'
+ return window_statusline.format
+ else:
+ def create_window_statusline_constructor(self):
+ startstr = b'%!' + self.pyeval.encode('ascii') + b'(\'powerline.statusline('
+ endstr = b')\')'
+ return lambda idx: (
+ startstr + str(idx).encode('ascii') + endstr
+ )
+
+ create_window_statusline_constructor.__doc__ = (
+ '''Create function which returns &l:stl value being given window index
+
+ Created function must return :py:class:`bytes` instance because this is
+ what ``window.options['statusline']`` returns (``window`` is
+ :py:class:`vim.Window` instance).
+
+ :return:
+ Function with type ``int → bytes``.
+ '''
+ )
+
+ default_log_stream = sys.stdout
+
+ def add_local_theme(self, key, config):
+ '''Add local themes at runtime (during vim session).
+
+ :param str key:
+ Matcher name (in format ``{matcher_module}.{module_attribute}`` or
+ ``{module_attribute}`` if ``{matcher_module}`` is
+ ``powerline.matchers.vim``). Function pointed by
+ ``{module_attribute}`` should be hashable and accept a dictionary
+ with information about current buffer and return boolean value
+ indicating whether current window matched conditions. See also
+ :ref:`local_themes key description <config-ext-local_themes>`.
+
+ :param dict config:
+ :ref:`Theme <config-themes>` dictionary.
+
+ :return:
+ ``True`` if theme was added successfully and ``False`` if theme with
+ the same matcher already exists.
+ '''
+ self.update_renderer()
+ matcher = self.get_matcher(key)
+ theme_config = {}
+ for cfg_path in self.theme_levels:
+ try:
+ lvl_config = self.load_config(cfg_path, 'theme')
+ except IOError:
+ pass
+ else:
+ mergedicts(theme_config, lvl_config)
+ mergedicts(theme_config, config)
+ try:
+ self.renderer.add_local_theme(matcher, {'config': theme_config})
+ except KeyError:
+ return False
+ else:
+ # Hack for local themes support: when reloading modules it is not
+ # guaranteed that .add_local_theme will be called once again, so
+ # this function arguments will be saved here for calling from
+ # .do_setup().
+ self.setup_kwargs.setdefault('_local_themes', []).append((key, config))
+ return True
+
+ get_encoding = staticmethod(get_vim_encoding)
+
+ def load_main_config(self):
+ main_config = _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides')
+ try:
+ use_var_handler = bool(int(vim_getvar('powerline_use_var_handler')))
+ except KeyError:
+ use_var_handler = False
+ if use_var_handler:
+ main_config.setdefault('common', {})
+ main_config['common'] = finish_common_config(self.get_encoding(), main_config['common'])
+ main_config['common']['log_file'].append(['powerline.vim.VimVarHandler', [['powerline_log_messages']]])
+ return main_config
+
+ def load_theme_config(self, name):
+ return _override_from(
+ super(VimPowerline, self).load_theme_config(name),
+ 'powerline_theme_overrides',
+ name
+ )
+
+ def get_local_themes(self, local_themes):
+ if not local_themes:
+ return {}
+
+ return dict((
+ (matcher, {'config': self.load_theme_config(val)})
+ for matcher, key, val in (
+ (
+ (None if k == '__tabline__' else self.get_matcher(k)),
+ k,
+ v
+ )
+ for k, v in local_themes.items()
+ ) if (
+ matcher or
+ key == '__tabline__'
+ )
+ ))
+
+ def get_matcher(self, match_name):
+ match_module, separator, match_function = match_name.rpartition('.')
+ if not separator:
+ match_module = 'powerline.matchers.{0}'.format(self.ext)
+ match_function = match_name
+ return self.get_module_attr(match_module, match_function, prefix='matcher_generator')
+
+ def get_config_paths(self):
+ try:
+ return vim_getvar('powerline_config_paths')
+ except KeyError:
+ return super(VimPowerline, self).get_config_paths()
+
+ def do_setup(self, pyeval=None, pycmd=None, can_replace_pyeval=True, _local_themes=()):
+ import __main__
+ if not pyeval:
+ pyeval = 'pyeval' if sys.version_info < (3,) else 'py3eval'
+ can_replace_pyeval = True
+ if not pycmd:
+ pycmd = get_default_pycmd()
+
+ set_pycmd(pycmd)
+
+ # pyeval() and vim.bindeval were both introduced in one patch
+ if (not hasattr(vim, 'bindeval') and can_replace_pyeval) or pyeval == 'PowerlinePyeval':
+ vim.command(('''
+ function! PowerlinePyeval(e)
+ {pycmd} powerline.do_pyeval()
+ endfunction
+ ''').format(pycmd=pycmd))
+ pyeval = 'PowerlinePyeval'
+
+ self.pyeval = pyeval
+ self.construct_window_statusline = self.create_window_statusline_constructor()
+
+ self.update_renderer()
+ __main__.powerline = self
+
+ try:
+ if (
+ bool(int(vim.eval('has(\'gui_running\') && argc() == 0')))
+ and not vim.current.buffer.name
+ and len(vim.windows) == 1
+ ):
+ # Hack to show startup screen. Problems in GUI:
+ # - Defining local value of &statusline option while computing
+ # global value purges startup screen.
+ # - Defining highlight group while computing statusline purges
+ # startup screen.
+ # This hack removes the “while computing statusline” part: both
+ # things are defined, but they are defined right now.
+ #
+ # The above condition disables this hack if no GUI is running,
+ # Vim did not open any files and there is only one window.
+ # Without GUI everything works, in other cases startup screen is
+ # not shown.
+ self.new_window()
+ except UnicodeDecodeError:
+ # vim.current.buffer.name may raise UnicodeDecodeError when using
+ # Python-3*. Fortunately, this means that current buffer is not
+ # empty buffer, so the above condition should be False.
+ pass
+
+ # Cannot have this in one line due to weird newline handling (in :execute
+ # context newline is considered part of the command in just the same cases
+ # when bar is considered part of the command (unless defining function
+ # inside :execute)). vim.command is :execute equivalent regarding this case.
+ vim.command('augroup Powerline')
+ vim.command(' autocmd! ColorScheme * :{pycmd} powerline.reset_highlight()'.format(pycmd=pycmd))
+ vim.command(' autocmd! VimLeavePre * :{pycmd} powerline.shutdown()'.format(pycmd=pycmd))
+ vim.command('augroup END')
+
+ # Hack for local themes support after reloading.
+ for args in _local_themes:
+ self.add_local_theme(*args)
+
+ def reset_highlight(self):
+ try:
+ self.renderer.reset_highlight()
+ except AttributeError:
+ # Renderer object appears only after first `.render()` call. Thus if
+ # ColorScheme event happens before statusline is drawn for the first
+ # time AttributeError will be thrown for the self.renderer. It is
+ # fine to ignore it: no renderer == no colors to reset == no need to
+ # do anything.
+ pass
+
+ def new_win_idx(self, window_id):
+ r = None
+
+ for window in vim.windows:
+ try:
+ curwindow_id = window.vars['powerline_window_id']
+ if r is not None and curwindow_id == window_id:
+ raise KeyError
+ except KeyError:
+ curwindow_id = self.last_window_id
+ self.last_window_id += 1
+ window.vars['powerline_window_id'] = curwindow_id
+ statusline = self.construct_window_statusline(curwindow_id)
+ if window.options['statusline'] != statusline:
+ window.options['statusline'] = statusline
+ if curwindow_id == window_id if window_id else window is vim.current.window:
+ r = (window, curwindow_id, window.number)
+ return r
+
+ def old_win_idx(self, window_id):
+ r = None
+ for winnr, window in zip(count(1), vim.windows):
+ curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id')
+ if curwindow_id and not (r is not None and curwindow_id == window_id):
+ curwindow_id = int(curwindow_id)
+ else:
+ curwindow_id = self.last_window_id
+ self.last_window_id += 1
+ self._vim_setwinvar(winnr, 'powerline_window_id', curwindow_id)
+ statusline = self.construct_window_statusline(curwindow_id)
+ if self._vim_getwinvar(winnr, '&statusline') != statusline:
+ self._vim_setwinvar(winnr, '&statusline', statusline)
+ if curwindow_id == window_id if window_id else window is vim.current.window:
+ r = (window, curwindow_id, winnr)
+ return r
+
+ def statusline(self, window_id):
+ window, window_id, winnr = self.win_idx(window_id) or (None, None, None)
+ if not window:
+ return FailedUnicode('No window {0}'.format(window_id))
+ return self.render(window, window_id, winnr)
+
+ def tabline(self):
+ r = self.win_idx(None)
+
+ if r:
+ return self.render(*r, is_tabline=True)
+ else:
+ win = vim.current.window
+ r = (
+ win,
+ win.vars.get('powerline_window_id', self.last_window_id),
+ win.number,
+ )
+ return self.render(*r, is_tabline=True)
+
+ def new_window(self):
+ return self.render(*self.win_idx(None))
+
+ @staticmethod
+ def do_pyeval():
+ '''Evaluate python string passed to PowerlinePyeval
+
+ Is here to reduce the number of requirements to __main__ globals to just
+ one powerline object (previously it required as well vim and json).
+ '''
+ import __main__
+ vim.command('return ' + json.dumps(eval(vim.eval('a:e'), __main__.__dict__)))
+
+ def setup_components(self, components):
+ if components is None:
+ components = ('statusline', 'tabline')
+ if 'statusline' in components:
+ # Is immediately changed after new_window function is run. Good for
+ # global value.
+ vim.command('set statusline=%!{pyeval}(\'powerline.new_window()\')'.format(
+ pyeval=self.pyeval))
+ if 'tabline' in components:
+ vim.command('set tabline=%!{pyeval}(\'powerline.tabline()\')'.format(
+ pyeval=self.pyeval))
+
+
+pycmd = None
+
+
+def set_pycmd(new_pycmd):
+ global pycmd
+ pycmd = new_pycmd
+
+
+def get_default_pycmd():
+ return 'python' if sys.version_info < (3,) else 'python3'
+
+
+def setup(*args, **kwargs):
+ powerline = VimPowerline()
+ return powerline.setup(*args, **kwargs)