# 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 `. :param dict config: :ref:`Theme ` 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): return self.render(*self.win_idx(None), 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)