diff --git a/powerline/__init__.py b/powerline/__init__.py index 845081fe..f48fad1d 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -8,6 +8,8 @@ import logging from powerline.colorscheme import Colorscheme +from threading import Lock + DEFAULT_SYSTEM_CONFIG_DIR = None @@ -97,64 +99,124 @@ class Powerline(object): environ=os.environ, getcwd=getattr(os, 'getcwdu', os.getcwd), home=None): + self.ext = ext + self.renderer_module = renderer_module or ext + self.run_once = run_once + self.logger = logger + self.environ = environ + self.getcwd = getcwd + self.home = home + self.config_paths = self.get_config_paths() - # Load main config file - config = self.load_main_config() - common_config = config['common'] - ext_config = config['ext'][ext] - self.ext = ext + self.renderer_lock = Lock() - # Load and initialize colorscheme - colorscheme_config = self.load_colorscheme_config(ext_config['colorscheme']) - colors_config = self.load_colors_config() - colorscheme = Colorscheme(colorscheme_config, colors_config) + self.prev_common_config = None + self.prev_ext_config = None - # Load and initialize extension theme - theme_config = self.load_theme_config(ext_config.get('theme', 'default')) - common_config['paths'] = [os.path.expanduser(path) for path in common_config.get('paths', [])] - self.import_paths = common_config['paths'] - theme_kwargs = { - 'ext': ext, - 'common_config': common_config, - 'run_once': run_once, - } - local_themes = self.get_local_themes(ext_config.get('local_themes')) + self.create_renderer(load_main_config=True, load_colors=True, load_colorscheme=True, load_theme=True) - # Load and initialize extension renderer - renderer_module_name = renderer_module or ext - renderer_module_import = 'powerline.renderers.{0}'.format(renderer_module_name) - try: - Renderer = __import__(renderer_module_import, fromlist=['renderer']).renderer - except ImportError as e: - sys.stderr.write('Error while importing renderer module: {0}\n'.format(e)) - sys.exit(1) - options = { - 'term_truecolor': common_config.get('term_truecolor', False), - 'ambiwidth': common_config.get('ambiwidth', 1), - 'tmux_escape': common_config.get('additional_escapes') == 'tmux', - 'screen_escape': common_config.get('additional_escapes') == 'screen', - } + def create_renderer(self, load_main_config=False, load_colors=False, load_colorscheme=False, load_theme=False): + '''(Re)create renderer object. Can be used after Powerline object was + successfully initialized. If any of the below parameters except + ``load_main_config`` is True renderer object will be recreated. - # Create logger - if not logger: - log_format = common_config.get('log_format', '%(asctime)s:%(levelname)s:%(message)s') - formatter = logging.Formatter(log_format) + :param bool load_main_config: + Determines whether main configuration file (:file:`config.json`) + should be loaded. If appropriate configuration changes implies + ``load_colorscheme`` and ``load_theme`` and recreation of renderer + object. Won’t trigger recreation if only unrelated configuration + changed. + :param bool load_colors: + Determines whether colors configuration from :file:`colors.json` + should be (re)loaded. + :param bool load_colorscheme: + Determines whether colorscheme configuration should be (re)loaded. + :param bool load_theme: + Determines whether theme configuration should be reloaded. - level = getattr(logging, common_config.get('log_level', 'WARNING')) - handler = self.get_log_handler(common_config) - handler.setLevel(level) - handler.setFormatter(formatter) + Note: reloading of local themes should be taken care of in renderer. + ''' + common_config_differs = False + ext_config_differs = False + if load_main_config: + config = self.load_main_config() + self.common_config = config['common'] + if self.common_config != self.prev_common_config: + common_config_differs = True + self.prev_common_config = self.common_config + self.common_config['paths'] = [os.path.expanduser(path) for path in self.common_config.get('paths', [])] + self.import_paths = self.common_config['paths'] - logger = logging.getLogger('powerline') - logger.setLevel(level) - logger.addHandler(handler) + if not self.logger: + log_format = self.common_config.get('log_format', '%(asctime)s:%(levelname)s:%(message)s') + formatter = logging.Formatter(log_format) - pl = PowerlineState(logger=logger, environ=environ, getcwd=getcwd, home=home) + level = getattr(logging, self.common_config.get('log_level', 'WARNING')) + handler = self.get_log_handler() + handler.setLevel(level) + handler.setFormatter(formatter) - self.renderer = Renderer(theme_config, local_themes, theme_kwargs, colorscheme, pl, **options) + self.logger = logging.getLogger('powerline') + self.logger.setLevel(level) + self.logger.addHandler(handler) - def get_log_handler(self, common_config): + self.pl = PowerlineState(logger=self.logger, environ=self.environ, getcwd=self.getcwd, home=self.home) + + self.renderer_options = { + 'term_truecolor': self.common_config.get('term_truecolor', False), + 'ambiwidth': self.common_config.get('ambiwidth', 1), + 'tmux_escape': self.common_config.get('additional_escapes') == 'tmux', + 'screen_escape': self.common_config.get('additional_escapes') == 'screen', + } + + self.theme_kwargs = { + 'ext': self.ext, + 'common_config': self.common_config, + 'run_once': self.run_once, + } + + self.ext_config = config['ext'][self.ext] + if self.ext_config != self.prev_ext_config: + ext_config_differs = True + if not self.prev_ext_config or self.ext_config.get('local_themes') != self.prev_ext_config.get('local_themes'): + self.local_themes = self.get_local_themes(self.ext_config.get('local_themes')) + load_colorscheme = (load_colorscheme + or not self.prev_ext_config + or self.prev_ext_config['colorscheme'] != self.ext_config['colorscheme']) + load_theme = (load_theme + or not self.prev_ext_config + or self.prev_ext_config['theme'] != self.ext_config['theme']) + + create_renderer = load_colors or load_colorscheme or load_theme or common_config_differs or ext_config_differs + + if load_colors: + colors_config = self.load_colors_config() + + if load_colorscheme or load_colors: + colorscheme_config = self.load_colorscheme_config(self.ext_config['colorscheme']) + self.colorscheme = Colorscheme(colorscheme_config, colors_config) + + if load_theme: + self.theme_config = self.load_theme_config(self.ext_config.get('theme', 'default')) + + if create_renderer: + renderer_module_import = 'powerline.renderers.{0}'.format(self.renderer_module) + try: + Renderer = __import__(renderer_module_import, fromlist=['renderer']).renderer + except Exception as e: + self.pl.exception('Failed to import renderer module: {0}', str(e)) + sys.exit(1) + + with self.renderer_lock: + self.renderer = Renderer(self.theme_config, + self.local_themes, + self.theme_kwargs, + self.colorscheme, + self.pl, + **self.renderer_options) + + def get_log_handler(self): '''Get log handler. :param dict common_config: @@ -162,7 +224,7 @@ class Powerline(object): :return: logging.Handler subclass. ''' - log_file = common_config.get('log_file', None) + log_file = self.common_config.get('log_file', None) if log_file: log_file = os.path.expanduser(log_file) log_dir = os.path.dirname(log_file) @@ -237,3 +299,16 @@ class Powerline(object): ``__init__`` arguments, refer to its documentation. ''' return None + + def render(self, *args, **kwargs): + '''Lock renderer from modifications and pass all arguments further to + ``self.renderer.render()``. + ''' + with self.renderer_lock: + return self.renderer.render(*args, **kwargs) + + def shutdown(self): + '''Lock renderer from modifications and run its ``.shutdown()`` method. + ''' + with self.renderer_lock: + self.renderer.shutdown() diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py index bdb42923..f91a5aed 100644 --- a/powerline/bindings/ipython/post_0_11.py +++ b/powerline/bindings/ipython/post_0_11.py @@ -24,7 +24,7 @@ class PowerlinePromptManager(PromptManager): def render(self, name, color=True, *args, **kwargs): width = None if name == 'in' else self.width - res, res_nocolor = self.powerline.renderer.render(output_raw=True, width=width, matcher_info=name, segment_info=self.powerline_segment_info) + res, res_nocolor = self.powerline.render(output_raw=True, width=width, matcher_info=name, segment_info=self.powerline_segment_info) self.txtwidth = len(res_nocolor) self.width = self.txtwidth return res if color else res_nocolor @@ -51,7 +51,7 @@ def load_ipython_extension(ip): ip.prompt_manager = PowerlinePromptManager(powerline=powerline, shell=ip.prompt_manager.shell) def shutdown_hook(): - powerline.renderer.shutdown() + powerline.shutdown() raise TryNext() ip.hooks.shutdown_hook.add(shutdown_hook) diff --git a/powerline/bindings/ipython/pre_0_11.py b/powerline/bindings/ipython/pre_0_11.py index 64f9b206..628c39bc 100644 --- a/powerline/bindings/ipython/pre_0_11.py +++ b/powerline/bindings/ipython/pre_0_11.py @@ -56,7 +56,7 @@ class PowerlinePrompt(BasePrompt): def set_p_str(self, width=None): self.p_str, self.p_str_nocolor = ( - self.powerline.renderer.render(output_raw=True, + self.powerline.render(output_raw=True, segment_info=self.powerline_segment_info, matcher_info=self.powerline_prompt_type, width=width) @@ -85,7 +85,7 @@ class PowerlinePrompt1(PowerlinePrompt): self.powerline_last_in['prompt_text_len'] = self.prompt_text_len def auto_rewrite(self): - return RewriteResult(self.powerline.renderer.render(matcher_info='rewrite', width=self.prompt_text_len, segment_info=self.powerline_segment_info) + return RewriteResult(self.powerline.render(matcher_info='rewrite', width=self.prompt_text_len, segment_info=self.powerline_segment_info) + (' ' * self.nrspaces)) @@ -128,7 +128,7 @@ def setup(**kwargs): raise TryNext() def shutdown_hook(): - powerline.renderer.shutdown() + powerline.shutdown() raise TryNext() ip.IP.hooks.late_startup_hook.add(late_startup_hook) diff --git a/powerline/bindings/qtile/widget.py b/powerline/bindings/qtile/widget.py index 71d8d3fa..6c1e6602 100644 --- a/powerline/bindings/qtile/widget.py +++ b/powerline/bindings/qtile/widget.py @@ -15,7 +15,7 @@ class Powerline(base._TextBox): def update(self): if not self.configured: return True - self.text = self.powerline.renderer.render(side='right') + self.text = self.powerline.render(side='right') self.bar.draw() return True diff --git a/powerline/bindings/vim/plugin/powerline.vim b/powerline/bindings/vim/plugin/powerline.vim index 83f87296..71b9fb99 100644 --- a/powerline/bindings/vim/plugin/powerline.vim +++ b/powerline/bindings/vim/plugin/powerline.vim @@ -67,7 +67,7 @@ endfunction function! Powerline(window_id) let winidx = index(map(range(1, winnr('$')), 's:GetWinID(v:val)'), a:window_id) let current = w:window_id is# a:window_id - return s:pyeval('powerline.renderer.render('. a:window_id .', '. winidx .', '. current .')') + return s:pyeval('powerline.render('. a:window_id .', '. winidx .', '. current .')') endfunction function! PowerlineNew() @@ -84,7 +84,7 @@ endfunction augroup Powerline autocmd! ColorScheme * :exec s:powerline_pycmd 'powerline.renderer.reset_highlight()' autocmd! VimEnter * :redrawstatus! - autocmd! VimLeave * :exec s:powerline_pycmd 'powerline.renderer.shutdown()' + autocmd! VimLeave * :exec s:powerline_pycmd 'powerline.shutdown()' augroup END exec s:powerline_pycmd 'powerline = VimPowerline()' diff --git a/powerline/bindings/zsh/__init__.py b/powerline/bindings/zsh/__init__.py index 420f0e86..4f00129a 100644 --- a/powerline/bindings/zsh/__init__.py +++ b/powerline/bindings/zsh/__init__.py @@ -10,7 +10,7 @@ used_powerlines = [] def shutdown(): for powerline in used_powerlines: - powerline.renderer.shutdown() + powerline.shutdown() def get_var_config(var): @@ -88,7 +88,7 @@ class Prompt(object): self.args = powerline.args def __str__(self): - r = self.powerline.renderer.render(width=zsh.columns(), side=self.side, segment_info=self.args) + r = self.powerline.render(width=zsh.columns(), side=self.side, segment_info=self.args) if type(r) is not str: if type(r) is bytes: return r.decode('utf-8') @@ -101,7 +101,7 @@ class Prompt(object): zsh.setvalue(self.savedpsvar, self.savedps) used_powerlines.remove(self.powerline) if self.powerline not in used_powerlines: - self.powerline.renderer.shutdown() + self.powerline.shutdown() def set_prompt(powerline, psvar, side): diff --git a/scripts/powerline b/scripts/powerline index 0926a760..dcc23fa5 100755 --- a/scripts/powerline +++ b/scripts/powerline @@ -17,7 +17,7 @@ if __name__ == '__main__': if 'PWD' in os.environ: kwargs['getcwd'] = lambda: os.environ['PWD'] powerline = ShellPowerline(args, run_once=True, **kwargs) - rendered = powerline.renderer.render(width=args.width, side=args.side, segment_info=args) + rendered = powerline.render(width=args.width, side=args.side, segment_info=args) try: sys.stdout.write(rendered) except UnicodeEncodeError: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index ec6b2917..8cb7531c 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -18,7 +18,7 @@ SBLOCK = chr(ord('S') - 0x40) def shutdown(powerline): from powerline.segments import common, vim try: - powerline.renderer.shutdown() + powerline.shutdown() finally: # After shutdown threads are useless, it is needed to recreate them. from imp import reload @@ -39,7 +39,7 @@ class TestConfig(TestCase): powerline = VimPowerline() def check_output(*args): - out = powerline.renderer.render(*args + (0 if mode == 'nc' else 1,)) + out = powerline.render(*args + (0 if mode == 'nc' else 1,)) if out in outputs: self.fail('Duplicate in set #{0} for mode {1!r} (previously defined in set #{2} for mode {3!r})'.format(i, mode, *outputs[out])) outputs[out] = (i, mode) @@ -69,27 +69,27 @@ class TestConfig(TestCase): from powerline.shell import ShellPowerline with replace_attr(common, 'urllib_read', urllib_read): powerline = ShellPowerline(Args(ext=['tmux']), run_once=False) - powerline.renderer.render() + powerline.render() powerline = ShellPowerline(Args(ext=['tmux']), run_once=False) - powerline.renderer.render() + powerline.render() shutdown(powerline) def test_zsh(self): from powerline.shell import ShellPowerline args = Args(last_pipe_status=[1, 0], ext=['shell'], renderer_module='zsh_prompt') powerline = ShellPowerline(args, run_once=False) - powerline.renderer.render(segment_info=args) + powerline.render(segment_info=args) powerline = ShellPowerline(args, run_once=False) - powerline.renderer.render(segment_info=args) + powerline.render(segment_info=args) shutdown(powerline) def test_bash(self): from powerline.shell import ShellPowerline args = Args(last_exit_code=1, ext=['shell'], renderer_module='bash_prompt', config=[('ext', {'shell': {'theme': 'default_leftonly'}})]) powerline = ShellPowerline(args, run_once=False) - powerline.renderer.render(segment_info=args) + powerline.render(segment_info=args) powerline = ShellPowerline(args, run_once=False) - powerline.renderer.render(segment_info=args) + powerline.render(segment_info=args) shutdown(powerline) def test_ipython(self): @@ -103,8 +103,8 @@ class TestConfig(TestCase): powerline = IpyPowerline() segment_info = Args(prompt_count=1) for prompt_type in ['in', 'in2', 'out', 'rewrite']: - powerline.renderer.render(matcher_info=prompt_type, segment_info=segment_info) - powerline.renderer.render(matcher_info=prompt_type, segment_info=segment_info) + powerline.render(matcher_info=prompt_type, segment_info=segment_info) + powerline.render(matcher_info=prompt_type, segment_info=segment_info) shutdown(powerline) def test_wm(self): @@ -113,7 +113,7 @@ class TestConfig(TestCase): reload(common) from powerline import Powerline with replace_attr(common, 'urllib_read', urllib_read): - Powerline(ext='wm', renderer_module='pango_markup', run_once=True).renderer.render() + Powerline(ext='wm', renderer_module='pango_markup', run_once=True).render() reload(common)