From 446eb42ea8c8457d192df20c83754a2b95ebf333 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 8 Apr 2013 23:46:29 +0400 Subject: [PATCH 1/8] Replace various __import__ calls with one Powerline.get_module_attr Also makes some more errors non-fatal: - Failure to import renderer class in case there is an existing renderer object - Failure to import segment function - Failure to import matcher function One of the purposes: create a function that is able to collect all imported modules to reload them (really purge out from `sys.modules` and let the python do its job when reimporting powerline and recreating Powerline and other objects). --- powerline/__init__.py | 42 +++++++++++++++++++++++++++++++++++++----- powerline/matcher.py | 19 ------------------- powerline/segment.py | 15 ++++++--------- powerline/theme.py | 3 ++- powerline/vim.py | 27 +++++++++++++++++++-------- 5 files changed, 64 insertions(+), 42 deletions(-) delete mode 100644 powerline/matcher.py diff --git a/powerline/__init__.py b/powerline/__init__.py index f2a7a166..3e192cbb 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -407,6 +407,9 @@ class Powerline(object): 'common_config': self.common_config, 'run_once': self.run_once, 'shutdown_event': self.shutdown_event, + # Note: creates implicit reference to self meaning + # reference cycle. + 'get_module_attr': self.get_module_attr, }, ) @@ -468,11 +471,12 @@ class Powerline(object): self.renderer_options['theme_config'] = self.load_theme_config(self.ext_config.get('theme', 'default')) if create_renderer: - try: - Renderer = __import__(self.renderer_module, fromlist=['renderer']).renderer - except Exception as e: - self.exception('Failed to import renderer module: {0}', str(e)) - sys.exit(1) + Renderer = self.get_module_attr(self.renderer_module, 'renderer') + if not Renderer: + if hasattr(self, 'renderer'): + return + else: + raise ImportError('Failed to obtain renderer') # Renderer updates configuration file via segments’ .startup thus it # should be locked to prevent state when configuration was updated, @@ -652,6 +656,34 @@ class Powerline(object): with self.cr_kwargs_lock: self.cr_kwargs.clear() + def get_module_attr(self, 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 = self.import_paths + sys.path + module = str(module) + attr = str(attr) + try: + return getattr(__import__(module, fromlist=(attr,)), attr) + except Exception as e: + self.pl.exception('Failed to import attr {0} from module {1}: {2}', attr, module, str(e), prefix=prefix) + return None + finally: + sys.path = oldpath + def render(self, *args, **kwargs): '''Update/create renderer if needed and pass all arguments further to ``self.renderer.render()``. diff --git a/powerline/matcher.py b/powerline/matcher.py deleted file mode 100644 index 37049e2f..00000000 --- a/powerline/matcher.py +++ /dev/null @@ -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 diff --git a/powerline/segment.py b/powerline/segment.py index 144f4623..b2a7086c 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -55,14 +55,11 @@ def get_segment_key(merge, *args, **kwargs): def get_function(data, segment): - oldpath = sys.path - sys.path = data['path'] + sys.path segment_module = str(segment.get('module', data['default_module'])) - name = str(segment['name']) - try: - return None, getattr(__import__(segment_module, fromlist=[name]), name), segment_module - finally: - sys.path = oldpath + function = data['get_module_attr'](segment_module, segment['name'], prefix='segment_generator') + if not function: + raise ImportError('Failed to obtain segment function') + return None, function, segment_module def get_string(data, segment): @@ -162,10 +159,10 @@ def process_segment(pl, side, segment_info, parsed_segments, 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 = { '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): diff --git a/powerline/theme.py b/powerline/theme.py index 378f8495..d2617dc5 100644 --- a/powerline/theme.py +++ b/powerline/theme.py @@ -29,6 +29,7 @@ class Theme(object): theme_config, common_config, pl, + get_module_attr, main_theme_config=None, run_once=False, shutdown_event=None): @@ -53,7 +54,7 @@ class Theme(object): theme_configs = [theme_config] if 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'],), theme_config['segments'].get('above', ())): self.segments.append(new_empty_segment_line()) diff --git a/powerline/vim.py b/powerline/vim.py index 2676ac29..a09e24cc 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -6,7 +6,6 @@ import sys from powerline.bindings.vim import vim_get_func, vim_getvar from powerline import Powerline from powerline.lib import mergedicts -from powerline.matcher import gen_matcher_getter import vim from itertools import count @@ -80,18 +79,30 @@ class VimPowerline(Powerline): ) def get_local_themes(self, local_themes): - self.get_matcher = gen_matcher_getter(self.ext, self.import_paths) - if not local_themes: return {} return dict(( - ( - (None if key == '__tabline__' else self.get_matcher(key)), - {'config': self.load_theme_config(val)} + (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__' ) - 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): try: From bbe3210bb6c1d7fbd5962e880301eb175631f9f4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 9 Apr 2013 00:00:28 +0400 Subject: [PATCH 2/8] Remove reference cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switches from powerline ↔ get_module_attr (as a bound method it contains reference to │ ↑ self) └────> theme to ┌────> pl, import_paths │ ↑ powerline → get_module_attr │ ↑ └────> theme This is a separate commit because I am not sure whether it makes sense to bother with this cycle. --- powerline/__init__.py | 64 +++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/powerline/__init__.py b/powerline/__init__.py index 3e192cbb..1b4c4cfd 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -270,6 +270,38 @@ else: raise exception +def gen_module_attr_getter(pl, import_paths): + 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: + 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): '''Main powerline class, entrance point for all powerline uses. Sets powerline up and loads the configuration. @@ -396,6 +428,8 @@ class Powerline(object): if not self.run_once: self.config_loader.set_watcher(self.common_config['watcher']) + self.get_module_attr = gen_module_attr_getter(self.pl, self.import_paths) + self.renderer_options.update( pl=self.pl, term_truecolor=self.common_config['term_truecolor'], @@ -407,8 +441,6 @@ class Powerline(object): 'common_config': self.common_config, 'run_once': self.run_once, 'shutdown_event': self.shutdown_event, - # Note: creates implicit reference to self meaning - # reference cycle. 'get_module_attr': self.get_module_attr, }, ) @@ -656,34 +688,6 @@ class Powerline(object): with self.cr_kwargs_lock: self.cr_kwargs.clear() - def get_module_attr(self, 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 = self.import_paths + sys.path - module = str(module) - attr = str(attr) - try: - return getattr(__import__(module, fromlist=(attr,)), attr) - except Exception as e: - self.pl.exception('Failed to import attr {0} from module {1}: {2}', attr, module, str(e), prefix=prefix) - return None - finally: - sys.path = oldpath - def render(self, *args, **kwargs): '''Update/create renderer if needed and pass all arguments further to ``self.renderer.render()``. From 85189e8b36b3cda9e7f2e2d8fc7ccb484ec5a3bc Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 9 Apr 2013 00:19:06 +0400 Subject: [PATCH 3/8] Add set_event argument to .shutdown() method --- powerline/__init__.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/powerline/__init__.py b/powerline/__init__.py index 1b4c4cfd..670ea198 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -726,15 +726,24 @@ class Powerline(object): pass yield FailedUnicode(safe_unicode(e)) - def shutdown(self): - '''Shut down all background threads. Must be run only prior to exiting - current application. + 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. ''' - self.shutdown_event.set() - try: - self.renderer.shutdown() - except AttributeError: - pass + if set_event: + self.shutdown_event.set() + try: + self.renderer.shutdown() + except AttributeError: + pass functions = tuple(self.cr_callbacks.values()) self.config_loader.unregister_functions(set(functions)) self.config_loader.unregister_missing(set(((self.find_config_files, function) for function in functions))) From 109525716119529b3c9eb110d5f053506d60b066 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 10 Apr 2013 20:04:22 +0400 Subject: [PATCH 4/8] Pop path from self.loaded on exception --- powerline/lib/config.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/powerline/lib/config.py b/powerline/lib/config.py index 522e5f45..d56b3c68 100644 --- a/powerline/lib/config.py +++ b/powerline/lib/config.py @@ -200,6 +200,10 @@ class ConfigLoader(MultiRunnedThread): except KeyError: pass self.exception('Error while loading {0}: {1}', path, str(e)) + try: + self.loaded.pop(path) + except KeyError: + pass def run(self): while self.interval is not None and not self.shutdown_event.is_set(): From 3c1ec1959f56b55b337704b3c4ac6a7ec7c31b86 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 15 Apr 2013 08:30:21 +0400 Subject: [PATCH 5/8] Record imported modules --- powerline/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/powerline/__init__.py b/powerline/__init__.py index 670ea198..67c3a0d9 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -270,7 +270,7 @@ else: raise exception -def gen_module_attr_getter(pl, import_paths): +def gen_module_attr_getter(pl, import_paths, imported_modules): def get_module_attr(module, attr, prefix='powerline'): '''Import module and get its attribute. @@ -292,6 +292,7 @@ def gen_module_attr_getter(pl, import_paths): 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) @@ -381,6 +382,7 @@ class Powerline(object): self.prev_common_config = None self.prev_ext_config = None self.pl = None + self.imported_modules = set() 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 @@ -428,7 +430,7 @@ class Powerline(object): if not self.run_once: self.config_loader.set_watcher(self.common_config['watcher']) - self.get_module_attr = gen_module_attr_getter(self.pl, self.import_paths) + self.get_module_attr = gen_module_attr_getter(self.pl, self.import_paths, self.imported_modules) self.renderer_options.update( pl=self.pl, From ca13bc53e444d0dd2e88ca050417d42932f54976 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 15 Apr 2013 08:31:02 +0400 Subject: [PATCH 6/8] Add setup function --- powerline/__init__.py | 8 ++ powerline/bindings/ipython/post_0_11.py | 4 +- powerline/bindings/ipython/pre_0_11.py | 4 +- powerline/bindings/vim/plugin/powerline.vim | 6 +- powerline/bindings/zsh/__init__.py | 1 + powerline/ipython.py | 4 + powerline/shell.py | 4 + powerline/vim.py | 105 +++++++++++--------- 8 files changed, 80 insertions(+), 56 deletions(-) diff --git a/powerline/__init__.py b/powerline/__init__.py index 67c3a0d9..a6958ba9 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -382,6 +382,7 @@ class Powerline(object): self.prev_common_config = None self.prev_ext_config = 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): @@ -728,6 +729,13 @@ class Powerline(object): pass yield FailedUnicode(safe_unicode(e)) + def setup(self, *args, **kwargs): + '''Setup the environment to use powerline. + + To be overridden by subclasses, this one only saves args and kwargs. + ''' + self.setup_args = (args, kwargs) + def shutdown(self, set_event=True): '''Shut down all background threads. diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py index ec3a4769..12aa8ef0 100644 --- a/powerline/bindings/ipython/post_0_11.py +++ b/powerline/bindings/ipython/post_0_11.py @@ -16,8 +16,8 @@ class IpythonInfo(object): class PowerlinePromptManager(PromptManager): def __init__(self, prompt_powerline, non_prompt_powerline, shell): - self.prompt_powerline = prompt_powerline - self.non_prompt_powerline = non_prompt_powerline + prompt_powerline.setup('prompt_powerline', self) + non_prompt_powerline.setup('non_prompt_powerline', self) self.powerline_segment_info = IpythonInfo(shell) self.shell = shell diff --git a/powerline/bindings/ipython/pre_0_11.py b/powerline/bindings/ipython/pre_0_11.py index ed6ad75e..68fb34f1 100644 --- a/powerline/bindings/ipython/pre_0_11.py +++ b/powerline/bindings/ipython/pre_0_11.py @@ -19,8 +19,8 @@ class IpythonInfo(object): class PowerlinePrompt(BasePrompt): def __init__(self, powerline, other_powerline, powerline_last_in, old_prompt): - self.powerline = powerline - self.other_powerline = other_powerline + powerline.setup('powerline', self) + other_powerline.setup('other_powerline', self) self.powerline_last_in = powerline_last_in self.powerline_segment_info = IpythonInfo(old_prompt.cache) self.cache = old_prompt.cache diff --git a/powerline/bindings/vim/plugin/powerline.vim b/powerline/bindings/vim/plugin/powerline.vim index cdf92726..e0367e8f 100644 --- a/powerline/bindings/vim/plugin/powerline.vim +++ b/powerline/bindings/vim/plugin/powerline.vim @@ -49,7 +49,7 @@ if !s:has_python endif 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 let s:pystr = "try:\n" let s:pystr .= " ".s:import_cmd."\n" @@ -120,8 +120,8 @@ endtry let s:can_replace_pyeval = !exists('g:powerline_pyeval') 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 'del powerline_setup' +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 VimPowerline' unlet s:can_replace_pyeval unlet s:pycmd diff --git a/powerline/bindings/zsh/__init__.py b/powerline/bindings/zsh/__init__.py index b6ccde1a..1f6ae40b 100644 --- a/powerline/bindings/zsh/__init__.py +++ b/powerline/bindings/zsh/__init__.py @@ -104,6 +104,7 @@ class Prompt(object): def __init__(self, powerline, side, theme, savedpsvar=None, savedps=None, above=False): self.powerline = powerline + powerline.setup(self) self.side = side self.above = above self.savedpsvar = savedpsvar diff --git a/powerline/ipython.py b/powerline/ipython.py index bc244d6d..29c165b1 100644 --- a/powerline/ipython.py +++ b/powerline/ipython.py @@ -55,3 +55,7 @@ class IpythonPowerline(Powerline): if name in self.theme_overrides: mergedicts(r, self.theme_overrides[name]) return r + + def setup(self, attr, obj): + setattr(obj, attr, self) + super(IpythonPowerline, self).setup(attr, obj) diff --git a/powerline/shell.py b/powerline/shell.py index 2d4152d2..b2fea9e7 100644 --- a/powerline/shell.py +++ b/powerline/shell.py @@ -43,6 +43,10 @@ class ShellPowerline(Powerline): 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): if not parser: diff --git a/powerline/vim.py b/powerline/vim.py index a09e24cc..3a0f7c8b 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -110,6 +110,59 @@ class VimPowerline(Powerline): except KeyError: 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 def get_segment_info(): return {} @@ -211,52 +264,6 @@ def get_default_pycmd(): return 'python' if sys.version_info < (3,) else 'python3' -def setup(pyeval=None, pycmd=None, can_replace_pyeval=True): - 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' - - 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') +def setup(*args, **kwargs): + powerline = VimPowerline() + return powerline.setup(*args, **kwargs) From 94354475b5d259d012e4d8eab8c270938dbcadf6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 15 Apr 2013 23:49:04 +0400 Subject: [PATCH 7/8] Add support for full powerline reloading at runtime Is not guaranteed to work in all cases. --- powerline/__init__.py | 58 +++++++++++++++++++++---- powerline/bindings/ipython/post_0_11.py | 4 +- powerline/bindings/ipython/pre_0_11.py | 4 +- powerline/ipython.py | 4 +- powerline/shell.py | 4 +- powerline/vim.py | 4 +- 6 files changed, 59 insertions(+), 19 deletions(-) diff --git a/powerline/__init__.py b/powerline/__init__.py index a6958ba9..716c8abf 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -336,14 +336,22 @@ class Powerline(object): Instance of the class that manages (re)loading of the configuration. ''' - def __init__(self, - ext, - renderer_module=None, - run_once=False, - logger=None, - use_daemon_threads=True, - shutdown_event=None, - config_loader=None): + def __init__(self, *args, **kwargs): + self.init_args = (args, kwargs) + self.init(*args, **kwargs) + + def init(self, + ext, + renderer_module=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. + ''' self.ext = ext self.run_once = run_once self.logger = logger @@ -732,10 +740,42 @@ class Powerline(object): def setup(self, *args, **kwargs): '''Setup the environment to use powerline. - To be overridden by subclasses, this one only saves args and kwargs. + To be overridden by subclasses, this one only saves args and kwargs and + unsets shutdown_event. ''' + self.shutdown_event.clear() self.setup_args = (args, kwargs) + def reload(self): + '''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. diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py index 12aa8ef0..e30d8797 100644 --- a/powerline/bindings/ipython/post_0_11.py +++ b/powerline/bindings/ipython/post_0_11.py @@ -43,12 +43,12 @@ class PowerlinePromptManager(PromptManager): class ConfigurableIpythonPowerline(IpythonPowerline): - def __init__(self, ip, is_prompt, old_widths): + def init(self, ip, is_prompt, old_widths): config = ip.config.Powerline self.config_overrides = config.get('config_overrides') self.theme_overrides = config.get('theme_overrides', {}) 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 diff --git a/powerline/bindings/ipython/pre_0_11.py b/powerline/bindings/ipython/pre_0_11.py index 68fb34f1..e740c8f3 100644 --- a/powerline/bindings/ipython/pre_0_11.py +++ b/powerline/bindings/ipython/pre_0_11.py @@ -86,11 +86,11 @@ class PowerlinePrompt2(PowerlinePromptOut): 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.theme_overrides = theme_overrides self.paths = paths - super(ConfigurableIpythonPowerline, self).__init__(is_prompt, old_widths) + super(ConfigurableIpythonPowerline, self).init(is_prompt, old_widths) def setup(**kwargs): diff --git a/powerline/ipython.py b/powerline/ipython.py index 29c165b1..7d675058 100644 --- a/powerline/ipython.py +++ b/powerline/ipython.py @@ -23,8 +23,8 @@ class RewriteResult(object): class IpythonPowerline(Powerline): - def __init__(self, is_prompt, old_widths): - super(IpythonPowerline, self).__init__( + def init(self, is_prompt, old_widths): + super(IpythonPowerline, self).init( 'ipython', renderer_module=('.prompt' if is_prompt else None), use_daemon_threads=True diff --git a/powerline/shell.py b/powerline/shell.py index b2fea9e7..cbd55fae 100644 --- a/powerline/shell.py +++ b/powerline/shell.py @@ -14,10 +14,10 @@ def mergeargs(argvalue): class ShellPowerline(Powerline): - def __init__(self, args, **kwargs): + def init(self, args, **kwargs): self.args = args 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): r = super(ShellPowerline, self).load_main_config() diff --git a/powerline/vim.py b/powerline/vim.py index 3a0f7c8b..3594a6c0 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -23,8 +23,8 @@ def _override_from(config, override_varname): class VimPowerline(Powerline): - def __init__(self, pyeval='PowerlinePyeval', **kwargs): - super(VimPowerline, self).__init__('vim', **kwargs) + def init(self, pyeval='PowerlinePyeval', **kwargs): + super(VimPowerline, self).init('vim', **kwargs) self.last_window_id = 1 self.pyeval = pyeval self.window_statusline = '%!' + pyeval + '(\'powerline.statusline({0})\')' From fe7aad7695bd091bba60f66e5feeda6229f3431c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 16 Aug 2014 18:30:25 +0400 Subject: [PATCH 8/8] Update documentation for `Powerline.init` --- powerline/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/powerline/__init__.py b/powerline/__init__.py index 716c8abf..dd3d37c3 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -350,7 +350,11 @@ class Powerline(object): config_loader=None): '''Do actual initialization. - __init__ function only stores the arguments. + __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.run_once = run_once