Merge pull request #436 from ZyX-I/powerline-reload

Add support for reloading powerline
This commit is contained in:
Nikolai Aleksandrovich Pavlov 2014-08-16 18:32:38 +04:00
commit 849bdb7dd2
12 changed files with 233 additions and 124 deletions

View File

@ -270,6 +270,39 @@ else:
raise exception raise exception
def gen_module_attr_getter(pl, import_paths, imported_modules):
def get_module_attr(module, attr, prefix='powerline'):
'''Import module and get its attribute.
Replaces ``from {module} import {attr}``.
:param str module:
Module name, will be passed as first argument to ``__import__``.
:param str attr:
Module attribute, will be passed to ``__import__`` as the only value
in ``fromlist`` tuple.
:return:
Attribute value or ``None``. Note: there is no way to distinguish
between successfull import of attribute equal to ``None`` and
unsuccessfull import.
'''
oldpath = sys.path
sys.path = import_paths + sys.path
module = str(module)
attr = str(attr)
try:
imported_modules.add(module)
return getattr(__import__(module, fromlist=(attr,)), attr)
except Exception as e:
pl.exception('Failed to import attr {0} from module {1}: {2}', attr, module, str(e), prefix=prefix)
return None
finally:
sys.path = oldpath
return get_module_attr
class Powerline(object): class Powerline(object):
'''Main powerline class, entrance point for all powerline uses. Sets '''Main powerline class, entrance point for all powerline uses. Sets
powerline up and loads the configuration. powerline up and loads the configuration.
@ -303,14 +336,26 @@ class Powerline(object):
Instance of the class that manages (re)loading of the configuration. Instance of the class that manages (re)loading of the configuration.
''' '''
def __init__(self, def __init__(self, *args, **kwargs):
ext, self.init_args = (args, kwargs)
renderer_module=None, self.init(*args, **kwargs)
run_once=False,
logger=None, def init(self,
use_daemon_threads=True, ext,
shutdown_event=None, renderer_module=None,
config_loader=None): run_once=False,
logger=None,
use_daemon_threads=True,
shutdown_event=None,
config_loader=None):
'''Do actual initialization.
__init__ function only stores the arguments and runs this function. This
function exists for powerline to be able to reload itself: it is easier
to make ``__init__`` store arguments and call overriddable ``init`` than
tell developers that each time they override Powerline.__init__ in
subclasses they must store actual arguments.
'''
self.ext = ext self.ext = ext
self.run_once = run_once self.run_once = run_once
self.logger = logger self.logger = logger
@ -349,6 +394,8 @@ class Powerline(object):
self.prev_common_config = None self.prev_common_config = None
self.prev_ext_config = None self.prev_ext_config = None
self.pl = None self.pl = None
self.setup_args = None
self.imported_modules = set()
def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False): def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False):
'''(Re)create renderer object. Can be used after Powerline object was '''(Re)create renderer object. Can be used after Powerline object was
@ -396,6 +443,8 @@ class Powerline(object):
if not self.run_once: if not self.run_once:
self.config_loader.set_watcher(self.common_config['watcher']) self.config_loader.set_watcher(self.common_config['watcher'])
self.get_module_attr = gen_module_attr_getter(self.pl, self.import_paths, self.imported_modules)
self.renderer_options.update( self.renderer_options.update(
pl=self.pl, pl=self.pl,
term_truecolor=self.common_config['term_truecolor'], term_truecolor=self.common_config['term_truecolor'],
@ -407,6 +456,7 @@ class Powerline(object):
'common_config': self.common_config, 'common_config': self.common_config,
'run_once': self.run_once, 'run_once': self.run_once,
'shutdown_event': self.shutdown_event, 'shutdown_event': self.shutdown_event,
'get_module_attr': self.get_module_attr,
}, },
) )
@ -468,11 +518,12 @@ class Powerline(object):
self.renderer_options['theme_config'] = self.load_theme_config(self.ext_config.get('theme', 'default')) self.renderer_options['theme_config'] = self.load_theme_config(self.ext_config.get('theme', 'default'))
if create_renderer: if create_renderer:
try: Renderer = self.get_module_attr(self.renderer_module, 'renderer')
Renderer = __import__(self.renderer_module, fromlist=['renderer']).renderer if not Renderer:
except Exception as e: if hasattr(self, 'renderer'):
self.exception('Failed to import renderer module: {0}', str(e)) return
sys.exit(1) else:
raise ImportError('Failed to obtain renderer')
# Renderer updates configuration file via segments .startup thus it # Renderer updates configuration file via segments .startup thus it
# should be locked to prevent state when configuration was updated, # should be locked to prevent state when configuration was updated,
@ -690,15 +741,63 @@ class Powerline(object):
pass pass
yield FailedUnicode(safe_unicode(e)) yield FailedUnicode(safe_unicode(e))
def shutdown(self): def setup(self, *args, **kwargs):
'''Shut down all background threads. Must be run only prior to exiting '''Setup the environment to use powerline.
current application.
To be overridden by subclasses, this one only saves args and kwargs and
unsets shutdown_event.
''' '''
self.shutdown_event.set() self.shutdown_event.clear()
try: self.setup_args = (args, kwargs)
self.renderer.shutdown()
except AttributeError: def reload(self):
pass '''Reload powerline after update.
Should handle most (but not all) powerline updates.
Purges out all powerline modules and modules imported by powerline for
segment and matcher functions. Requires defining ``setup`` function that
updates reference to main powerline object.
.. warning::
Not guaranteed to work properly, use it at your own risk. It
may break your python code.
'''
from imp import reload
modules = self.imported_modules | set((module for module in sys.modules if module.startswith('powerline')))
modules_holder = []
for module in modules:
try:
# Needs to hold module to prevent garbage collecting until they
# are all reloaded.
modules_holder.append(sys.modules.pop(module))
except KeyError:
pass
PowerlineClass = getattr(__import__(self.__module__, fromlist=(self.__class__.__name__,)), self.__class__.__name__)
self.shutdown(set_event=True)
init_args, init_kwargs = self.init_args
powerline = PowerlineClass(*init_args, **init_kwargs)
setup_args, setup_kwargs = self.setup_args
powerline.setup(*setup_args, **setup_kwargs)
def shutdown(self, set_event=True):
'''Shut down all background threads.
:param bool set_event:
Set ``shutdown_event`` and call ``renderer.shutdown`` which should
shut down all threads. Set it to False unless you are exiting an
application.
If set to False this does nothing more then resolving reference
cycle ``powerline config_loader bound methods powerline`` by
unsubscribing from config_loader events.
'''
if set_event:
self.shutdown_event.set()
try:
self.renderer.shutdown()
except AttributeError:
pass
functions = tuple(self.cr_callbacks.values()) functions = tuple(self.cr_callbacks.values())
self.config_loader.unregister_functions(set(functions)) self.config_loader.unregister_functions(set(functions))
self.config_loader.unregister_missing(set(((self.find_config_files, function) for function in functions))) self.config_loader.unregister_missing(set(((self.find_config_files, function) for function in functions)))

View File

@ -16,8 +16,8 @@ class IpythonInfo(object):
class PowerlinePromptManager(PromptManager): class PowerlinePromptManager(PromptManager):
def __init__(self, prompt_powerline, non_prompt_powerline, shell): def __init__(self, prompt_powerline, non_prompt_powerline, shell):
self.prompt_powerline = prompt_powerline prompt_powerline.setup('prompt_powerline', self)
self.non_prompt_powerline = non_prompt_powerline non_prompt_powerline.setup('non_prompt_powerline', self)
self.powerline_segment_info = IpythonInfo(shell) self.powerline_segment_info = IpythonInfo(shell)
self.shell = shell self.shell = shell
@ -43,12 +43,12 @@ class PowerlinePromptManager(PromptManager):
class ConfigurableIpythonPowerline(IpythonPowerline): class ConfigurableIpythonPowerline(IpythonPowerline):
def __init__(self, ip, is_prompt, old_widths): def init(self, ip, is_prompt, old_widths):
config = ip.config.Powerline config = ip.config.Powerline
self.config_overrides = config.get('config_overrides') self.config_overrides = config.get('config_overrides')
self.theme_overrides = config.get('theme_overrides', {}) self.theme_overrides = config.get('theme_overrides', {})
self.paths = config.get('paths') self.paths = config.get('paths')
super(ConfigurableIpythonPowerline, self).__init__(is_prompt, old_widths) super(ConfigurableIpythonPowerline, self).init(is_prompt, old_widths)
old_prompt_manager = None old_prompt_manager = None

View File

@ -19,8 +19,8 @@ class IpythonInfo(object):
class PowerlinePrompt(BasePrompt): class PowerlinePrompt(BasePrompt):
def __init__(self, powerline, other_powerline, powerline_last_in, old_prompt): def __init__(self, powerline, other_powerline, powerline_last_in, old_prompt):
self.powerline = powerline powerline.setup('powerline', self)
self.other_powerline = other_powerline other_powerline.setup('other_powerline', self)
self.powerline_last_in = powerline_last_in self.powerline_last_in = powerline_last_in
self.powerline_segment_info = IpythonInfo(old_prompt.cache) self.powerline_segment_info = IpythonInfo(old_prompt.cache)
self.cache = old_prompt.cache self.cache = old_prompt.cache
@ -86,11 +86,11 @@ class PowerlinePrompt2(PowerlinePromptOut):
class ConfigurableIpythonPowerline(IpythonPowerline): class ConfigurableIpythonPowerline(IpythonPowerline):
def __init__(self, is_prompt, old_widths, config_overrides=None, theme_overrides={}, paths=None): def init(self, is_prompt, old_widths, config_overrides=None, theme_overrides={}, paths=None):
self.config_overrides = config_overrides self.config_overrides = config_overrides
self.theme_overrides = theme_overrides self.theme_overrides = theme_overrides
self.paths = paths self.paths = paths
super(ConfigurableIpythonPowerline, self).__init__(is_prompt, old_widths) super(ConfigurableIpythonPowerline, self).init(is_prompt, old_widths)
def setup(**kwargs): def setup(**kwargs):

View File

@ -49,7 +49,7 @@ if !s:has_python
endif endif
unlet s:has_python unlet s:has_python
let s:import_cmd = 'from powerline.vim import setup as powerline_setup' let s:import_cmd = 'from powerline.vim import VimPowerline'
try try
let s:pystr = "try:\n" let s:pystr = "try:\n"
let s:pystr .= " ".s:import_cmd."\n" let s:pystr .= " ".s:import_cmd."\n"
@ -120,8 +120,8 @@ endtry
let s:can_replace_pyeval = !exists('g:powerline_pyeval') let s:can_replace_pyeval = !exists('g:powerline_pyeval')
execute s:pycmd 'import vim' execute s:pycmd 'import vim'
execute s:pycmd 'powerline_setup(pyeval=vim.eval("s:pyeval"), pycmd=vim.eval("s:pycmd"), can_replace_pyeval=int(vim.eval("s:can_replace_pyeval")))' execute s:pycmd 'VimPowerline().setup(pyeval=vim.eval("s:pyeval"), pycmd=vim.eval("s:pycmd"), can_replace_pyeval=int(vim.eval("s:can_replace_pyeval")))'
execute s:pycmd 'del powerline_setup' execute s:pycmd 'del VimPowerline'
unlet s:can_replace_pyeval unlet s:can_replace_pyeval
unlet s:pycmd unlet s:pycmd

View File

@ -104,6 +104,7 @@ class Prompt(object):
def __init__(self, powerline, side, theme, savedpsvar=None, savedps=None, above=False): def __init__(self, powerline, side, theme, savedpsvar=None, savedps=None, above=False):
self.powerline = powerline self.powerline = powerline
powerline.setup(self)
self.side = side self.side = side
self.above = above self.above = above
self.savedpsvar = savedpsvar self.savedpsvar = savedpsvar

View File

@ -23,8 +23,8 @@ class RewriteResult(object):
class IpythonPowerline(Powerline): class IpythonPowerline(Powerline):
def __init__(self, is_prompt, old_widths): def init(self, is_prompt, old_widths):
super(IpythonPowerline, self).__init__( super(IpythonPowerline, self).init(
'ipython', 'ipython',
renderer_module=('.prompt' if is_prompt else None), renderer_module=('.prompt' if is_prompt else None),
use_daemon_threads=True use_daemon_threads=True
@ -55,3 +55,7 @@ class IpythonPowerline(Powerline):
if name in self.theme_overrides: if name in self.theme_overrides:
mergedicts(r, self.theme_overrides[name]) mergedicts(r, self.theme_overrides[name])
return r return r
def setup(self, attr, obj):
setattr(obj, attr, self)
super(IpythonPowerline, self).setup(attr, obj)

View File

@ -200,6 +200,10 @@ class ConfigLoader(MultiRunnedThread):
except KeyError: except KeyError:
pass pass
self.exception('Error while loading {0}: {1}', path, str(e)) self.exception('Error while loading {0}: {1}', path, str(e))
try:
self.loaded.pop(path)
except KeyError:
pass
def run(self): def run(self):
while self.interval is not None and not self.shutdown_event.is_set(): while self.interval is not None and not self.shutdown_event.is_set():

View File

@ -1,19 +0,0 @@
# vim:fileencoding=utf-8:noet
from __future__ import absolute_import
import sys
def gen_matcher_getter(ext, import_paths):
def get(match_name):
match_module, separator, match_function = match_name.rpartition('.')
if not separator:
match_module = 'powerline.matchers.{0}'.format(ext)
match_function = match_name
oldpath = sys.path
sys.path = import_paths + sys.path
try:
return getattr(__import__(str(match_module), fromlist=[str(match_function)]), match_function)
finally:
sys.path = oldpath
return get

View File

@ -55,14 +55,11 @@ def get_segment_key(merge, *args, **kwargs):
def get_function(data, segment): def get_function(data, segment):
oldpath = sys.path
sys.path = data['path'] + sys.path
segment_module = str(segment.get('module', data['default_module'])) segment_module = str(segment.get('module', data['default_module']))
name = str(segment['name']) function = data['get_module_attr'](segment_module, segment['name'], prefix='segment_generator')
try: if not function:
return None, getattr(__import__(segment_module, fromlist=[name]), name), segment_module raise ImportError('Failed to obtain segment function')
finally: return None, function, segment_module
sys.path = oldpath
def get_string(data, segment): def get_string(data, segment):
@ -162,10 +159,10 @@ def process_segment(pl, side, segment_info, parsed_segments, segment):
parsed_segments.append(segment) parsed_segments.append(segment)
def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=None): def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, get_module_attr):
data = { data = {
'default_module': default_module or 'powerline.segments.' + ext, 'default_module': default_module or 'powerline.segments.' + ext,
'path': common_config['paths'], 'get_module_attr': get_module_attr,
} }
def get_key(merge, segment, module, key, default=None): def get_key(merge, segment, module, key, default=None):

View File

@ -14,10 +14,10 @@ def mergeargs(argvalue):
class ShellPowerline(Powerline): class ShellPowerline(Powerline):
def __init__(self, args, **kwargs): def init(self, args, **kwargs):
self.args = args self.args = args
self.theme_option = args.theme_option self.theme_option = args.theme_option
super(ShellPowerline, self).__init__(args.ext[0], args.renderer_module, **kwargs) super(ShellPowerline, self).init(args.ext[0], args.renderer_module, **kwargs)
def load_main_config(self): def load_main_config(self):
r = super(ShellPowerline, self).load_main_config() r = super(ShellPowerline, self).load_main_config()
@ -43,6 +43,10 @@ class ShellPowerline(Powerline):
for key, val in local_themes.items() for key, val in local_themes.items()
)) ))
def setup(self, obj):
obj.powerline = self
super(ShellPowerline, self).setup(obj)
def get_argparser(parser=None, *args, **kwargs): def get_argparser(parser=None, *args, **kwargs):
if not parser: if not parser:

View File

@ -29,6 +29,7 @@ class Theme(object):
theme_config, theme_config,
common_config, common_config,
pl, pl,
get_module_attr,
main_theme_config=None, main_theme_config=None,
run_once=False, run_once=False,
shutdown_event=None): shutdown_event=None):
@ -53,7 +54,7 @@ class Theme(object):
theme_configs = [theme_config] theme_configs = [theme_config]
if main_theme_config: if main_theme_config:
theme_configs.append(main_theme_config) theme_configs.append(main_theme_config)
get_segment = gen_segment_getter(pl, ext, common_config, theme_configs, theme_config.get('default_module')) get_segment = gen_segment_getter(pl, ext, common_config, theme_configs, theme_config.get('default_module'), get_module_attr)
for segdict in itertools.chain((theme_config['segments'],), for segdict in itertools.chain((theme_config['segments'],),
theme_config['segments'].get('above', ())): theme_config['segments'].get('above', ())):
self.segments.append(new_empty_segment_line()) self.segments.append(new_empty_segment_line())

View File

@ -6,7 +6,6 @@ import sys
from powerline.bindings.vim import vim_get_func, vim_getvar from powerline.bindings.vim import vim_get_func, vim_getvar
from powerline import Powerline from powerline import Powerline
from powerline.lib import mergedicts from powerline.lib import mergedicts
from powerline.matcher import gen_matcher_getter
import vim import vim
from itertools import count from itertools import count
@ -24,8 +23,8 @@ def _override_from(config, override_varname):
class VimPowerline(Powerline): class VimPowerline(Powerline):
def __init__(self, pyeval='PowerlinePyeval', **kwargs): def init(self, pyeval='PowerlinePyeval', **kwargs):
super(VimPowerline, self).__init__('vim', **kwargs) super(VimPowerline, self).init('vim', **kwargs)
self.last_window_id = 1 self.last_window_id = 1
self.pyeval = pyeval self.pyeval = pyeval
self.window_statusline = '%!' + pyeval + '(\'powerline.statusline({0})\')' self.window_statusline = '%!' + pyeval + '(\'powerline.statusline({0})\')'
@ -80,18 +79,30 @@ class VimPowerline(Powerline):
) )
def get_local_themes(self, local_themes): def get_local_themes(self, local_themes):
self.get_matcher = gen_matcher_getter(self.ext, self.import_paths)
if not local_themes: if not local_themes:
return {} return {}
return dict(( return dict((
( (matcher, {'config': self.load_theme_config(val)})
(None if key == '__tabline__' else self.get_matcher(key)), for matcher, key, val in (
{'config': self.load_theme_config(val)} (
(None if k == '__tabline__' else self.get_matcher(k)),
k,
v
)
for k, v in local_themes.items()
) if (
matcher or
key == '__tabline__'
) )
for key, val in local_themes.items()) ))
)
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): def get_config_paths(self):
try: try:
@ -99,6 +110,59 @@ class VimPowerline(Powerline):
except KeyError: except KeyError:
return super(VimPowerline, self).get_config_paths() return super(VimPowerline, self).get_config_paths()
def setup(self, pyeval=None, pycmd=None, can_replace_pyeval=True):
super(VimPowerline, self).setup()
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:
vim.command(('''
function! PowerlinePyeval(e)
{pycmd} powerline.do_pyeval()
endfunction
''').format(pycmd=pycmd))
pyeval = 'PowerlinePyeval'
self.pyeval = pyeval
self.window_statusline = '%!' + pyeval + '(\'powerline.statusline({0})\')'
self.update_renderer()
__main__.powerline = self
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()
# 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')
@staticmethod @staticmethod
def get_segment_info(): def get_segment_info():
return {} return {}
@ -200,52 +264,6 @@ def get_default_pycmd():
return 'python' if sys.version_info < (3,) else 'python3' return 'python' if sys.version_info < (3,) else 'python3'
def setup(pyeval=None, pycmd=None, can_replace_pyeval=True): def setup(*args, **kwargs):
import __main__ powerline = VimPowerline()
if not pyeval: return powerline.setup(*args, **kwargs)
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:
vim.command(('''
function! PowerlinePyeval(e)
{pycmd} powerline.do_pyeval()
endfunction
''').format(pycmd=pycmd))
pyeval = 'PowerlinePyeval'
powerline = VimPowerline(pyeval)
powerline.update_renderer()
__main__.powerline = powerline
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.
powerline.new_window()
# 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')