diff --git a/docs/source/develop/segments.rst b/docs/source/develop/segments.rst index 63ea35db..1715d3a7 100644 --- a/docs/source/develop/segments.rst +++ b/docs/source/develop/segments.rst @@ -53,6 +53,8 @@ Detailed description of used dictionary keys: Text displayed by segment. Should be a ``unicode`` (Python2) or ``str`` (Python3) instance. +.. _dev-segments-draw_inner_divider: + ``draw_hard_divider``, ``draw_soft_divider``, ``draw_inner_divider`` Determines whether given divider should be drawn. All have the same meaning as :ref:`the similar keys in configuration ` diff --git a/docs/source/tips-and-tricks.rst b/docs/source/tips-and-tricks.rst index b2216b12..4be6c764 100644 --- a/docs/source/tips-and-tricks.rst +++ b/docs/source/tips-and-tricks.rst @@ -49,3 +49,46 @@ of the fonts play a role in whether or not the > or the < separators showing up or not. Using font size 12, glyphs on the right hand side of the powerline are present, but the ones on the left don't. Pixel size 14, brings the reverse problem. Font size 13 seems to work just fine. + +Reloading powerline after update +================================ + +Once you have updated powerline you generally have the following options: + +#. Restart the application you are using it in. This is the safest one. Will not + work if the application uses ``powerline-daemon``. +#. For shell and tmux bindings (except for zsh with libzpython): do not do + anything if you do not use ``powerline-daemon``, run ``powerline-daemon + --replace`` if you do. +#. Use powerline reloading feature. + + .. warning:: + This feature is an unsafe one. It is not guaranteed to work always, it may + render your Python constantly error out in place of displaying powerline + and sometimes may render your application useless, forcing you to + restart. + + *Do not report any bugs occurred when using this feature unless you know + both what caused it and how this can be fixed.* + + * When using zsh with libzpython use + + .. code-block:: bash + + powerline-reload + + .. note:: This shell function is only defined when using libzpython. + + * When using IPython use + + :: + + %powerline reload + + * When using Vim use + + .. code-block:: Vim + + py powerline.reload() + " or (depending on Python version you are using) + py3 powerline.reload() diff --git a/powerline/__init__.py b/powerline/__init__.py index dd3d37c3..c19b0424 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -744,11 +744,21 @@ 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 and - unsets shutdown_event. + Must not be overridden by subclasses. This one only saves setup + arguments for :py:meth:`reload` method and calls :py:meth:`do_setup`. ''' self.shutdown_event.clear() self.setup_args = (args, kwargs) + self.do_setup(*args, **kwargs) + + @staticmethod + def do_setup(): + '''Function that does initialization + + Should be overridden by subclasses. May accept any number of regular or + keyword arguments. + ''' + pass def reload(self): '''Reload powerline after update. @@ -764,6 +774,7 @@ class Powerline(object): may break your python code. ''' from imp import reload + import sys modules = self.imported_modules | set((module for module in sys.modules if module.startswith('powerline'))) modules_holder = [] for module in modules: diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py index e30d8797..5586fee4 100644 --- a/powerline/bindings/ipython/post_0_11.py +++ b/powerline/bindings/ipython/post_0_11.py @@ -1,11 +1,28 @@ # vim:fileencoding=utf-8:noet -from powerline.ipython import IpythonPowerline, RewriteResult + +from weakref import ref + +from powerline.ipython import IPythonPowerline, RewriteResult from IPython.core.prompts import PromptManager -from IPython.core.hooks import TryNext +from IPython.core.magic import Magics, magics_class, line_magic -class IpythonInfo(object): +@magics_class +class PowerlineMagics(Magics): + def __init__(self, ip, powerline): + super(PowerlineMagics, self).__init__(ip) + self._powerline = powerline + + @line_magic + def powerline(self, line): + if line == 'reload': + self._powerline.reload() + else: + raise ValueError('Expected `reload`, but got {0}'.format(line)) + + +class IPythonInfo(object): def __init__(self, shell): self._shell = shell @@ -15,18 +32,14 @@ class IpythonInfo(object): class PowerlinePromptManager(PromptManager): - def __init__(self, prompt_powerline, non_prompt_powerline, shell): - prompt_powerline.setup('prompt_powerline', self) - non_prompt_powerline.setup('non_prompt_powerline', self) - self.powerline_segment_info = IpythonInfo(shell) + def __init__(self, powerline, shell): + self.powerline = powerline + self.powerline_segment_info = IPythonInfo(shell) self.shell = shell def render(self, name, color=True, *args, **kwargs): - if name == 'out' or name == 'rewrite': - powerline = self.non_prompt_powerline - else: - powerline = self.prompt_powerline - res = powerline.render( + res = self.powerline.render( + is_prompt=name.startswith('in'), side='left', output_width=True, output_raw=not color, @@ -42,13 +55,35 @@ class PowerlinePromptManager(PromptManager): return ret -class ConfigurableIpythonPowerline(IpythonPowerline): - def init(self, ip, is_prompt, old_widths): +class ShutdownHook(object): + powerline = lambda: None + + def __call__(self): + from IPython.core.hooks import TryNext + powerline = self.powerline() + if powerline is not None: + powerline.shutdown() + raise TryNext() + + +class ConfigurableIPythonPowerline(IPythonPowerline): + def init(self, ip): 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() + + def do_setup(self, ip, shutdown_hook): + prompt_manager = PowerlinePromptManager( + powerline=self, + shell=ip.prompt_manager.shell, + ) + magics = PowerlineMagics(ip, self) + shutdown_hook.powerline = ref(self) + + ip.prompt_manager = prompt_manager + ip.register_magics(magics) old_prompt_manager = None @@ -56,22 +91,12 @@ old_prompt_manager = None def load_ipython_extension(ip): global old_prompt_manager - old_prompt_manager = ip.prompt_manager - old_widths = {} - prompt_powerline = ConfigurableIpythonPowerline(ip, True, old_widths) - non_prompt_powerline = ConfigurableIpythonPowerline(ip, False, old_widths) - ip.prompt_manager = PowerlinePromptManager( - prompt_powerline=prompt_powerline, - non_prompt_powerline=non_prompt_powerline, - shell=ip.prompt_manager.shell - ) + powerline = ConfigurableIPythonPowerline(ip) + shutdown_hook = ShutdownHook() - def shutdown_hook(): - prompt_powerline.shutdown() - non_prompt_powerline.shutdown() - raise TryNext() + powerline.setup(ip, shutdown_hook) 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 e740c8f3..d5389efe 100644 --- a/powerline/bindings/ipython/pre_0_11.py +++ b/powerline/bindings/ipython/pre_0_11.py @@ -1,14 +1,18 @@ # vim:fileencoding=utf-8:noet -from powerline.ipython import IpythonPowerline, RewriteResult + +import re + +from weakref import ref + +from powerline.ipython import IPythonPowerline, RewriteResult from powerline.lib.unicode import string + from IPython.Prompts import BasePrompt from IPython.ipapi import get as get_ipython from IPython.ipapi import TryNext -import re - -class IpythonInfo(object): +class IPythonInfo(object): def __init__(self, cache): self._cache = cache @@ -18,11 +22,10 @@ class IpythonInfo(object): class PowerlinePrompt(BasePrompt): - def __init__(self, powerline, other_powerline, powerline_last_in, old_prompt): - powerline.setup('powerline', self) - other_powerline.setup('other_powerline', self) + def __init__(self, powerline, powerline_last_in, old_prompt): + self.powerline = powerline 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 if hasattr(old_prompt, 'sep'): self.sep = old_prompt.sep @@ -35,6 +38,7 @@ class PowerlinePrompt(BasePrompt): def set_p_str(self): self.p_str, self.p_str_nocolor, self.powerline_prompt_width = ( self.powerline.render( + is_prompt=self.powerline_is_prompt, side='left', output_raw=True, output_width=True, @@ -50,6 +54,7 @@ class PowerlinePrompt(BasePrompt): class PowerlinePrompt1(PowerlinePrompt): powerline_prompt_type = 'in' + powerline_is_prompt = True rspace = re.compile(r'(\s*)$') def __str__(self): @@ -64,7 +69,8 @@ class PowerlinePrompt1(PowerlinePrompt): self.powerline_last_in['nrspaces'] = self.nrspaces def auto_rewrite(self): - return RewriteResult(self.other_powerline.render( + return RewriteResult(self.powerline.render( + is_prompt=False, side='left', matcher_info='rewrite', segment_info=self.powerline_segment_info) + (' ' * self.nrspaces) @@ -73,6 +79,7 @@ class PowerlinePrompt1(PowerlinePrompt): class PowerlinePromptOut(PowerlinePrompt): powerline_prompt_type = 'out' + powerline_is_prompt = False def set_p_str(self): super(PowerlinePromptOut, self).set_p_str() @@ -83,37 +90,55 @@ class PowerlinePromptOut(PowerlinePrompt): class PowerlinePrompt2(PowerlinePromptOut): powerline_prompt_type = 'in2' + powerline_is_prompt = True -class ConfigurableIpythonPowerline(IpythonPowerline): - def init(self, is_prompt, old_widths, config_overrides=None, theme_overrides={}, paths=None): +class ConfigurableIPythonPowerline(IPythonPowerline): + def init(self, 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() + + def ipython_magic(self, ip, parameter_s=''): + if parameter_s == 'reload': + self.reload() + else: + raise ValueError('Expected `reload`, but got {0}'.format(parameter_s)) + + def do_setup(self, ip, shutdown_hook): + last_in = {'nrspaces': 0} + for attr, prompt_class in ( + ('prompt1', PowerlinePrompt1), + ('prompt2', PowerlinePrompt2), + ('prompt_out', PowerlinePromptOut) + ): + old_prompt = getattr(ip.IP.outputcache, attr) + prompt = prompt_class(self, last_in, old_prompt) + setattr(ip.IP.outputcache, attr, prompt) + ip.expose_magic('powerline', self.ipython_magic) + shutdown_hook.powerline = ref(self) + + +class ShutdownHook(object): + powerline = lambda: None + + def __call__(self): + from IPython.ipapi import TryNext + powerline = self.powerline() + if powerline is not None: + powerline.shutdown() + raise TryNext() def setup(**kwargs): ip = get_ipython() - old_widths = {} - prompt_powerline = ConfigurableIpythonPowerline(True, old_widths, **kwargs) - non_prompt_powerline = ConfigurableIpythonPowerline(False, old_widths, **kwargs) + powerline = ConfigurableIPythonPowerline(**kwargs) + shutdown_hook = ShutdownHook() def late_startup_hook(): - last_in = {'nrspaces': 0} - for attr, prompt_class, powerline, other_powerline in ( - ('prompt1', PowerlinePrompt1, prompt_powerline, non_prompt_powerline), - ('prompt2', PowerlinePrompt2, prompt_powerline, None), - ('prompt_out', PowerlinePromptOut, non_prompt_powerline, None) - ): - old_prompt = getattr(ip.IP.outputcache, attr) - setattr(ip.IP.outputcache, attr, prompt_class(powerline, other_powerline, last_in, old_prompt)) - raise TryNext() - - def shutdown_hook(): - prompt_powerline.shutdown() - non_prompt_powerline.shutdown() + powerline.setup(ip, shutdown_hook) raise TryNext() ip.IP.hooks.late_startup_hook.add(late_startup_hook) diff --git a/powerline/bindings/zsh/__init__.py b/powerline/bindings/zsh/__init__.py index 1f6ae40b..ad3fa90b 100644 --- a/powerline/bindings/zsh/__init__.py +++ b/powerline/bindings/zsh/__init__.py @@ -1,18 +1,22 @@ # vim:fileencoding=utf-8:noet from __future__ import absolute_import, unicode_literals, division, print_function -import zsh import atexit + +import zsh + +from weakref import WeakValueDictionary, ref + from powerline.shell import ShellPowerline from powerline.lib import parsedotval from powerline.lib.unicode import unicode -used_powerlines = [] +used_powerlines = WeakValueDictionary() def shutdown(): - for powerline in used_powerlines: + for powerline in tuple(used_powerlines.values()): powerline.shutdown() @@ -94,17 +98,28 @@ environ = Environment() class ZshPowerline(ShellPowerline): + def init(self, **kwargs): + super(ZshPowerline, self).init(Args(), **kwargs) + def precmd(self): self.args.last_pipe_status = zsh.pipestatus() self.args.last_exit_code = zsh.last_exit_code() + def do_setup(self, zsh_globals): + set_prompt(self, 'PS1', 'left', None, above=True) + set_prompt(self, 'RPS1', 'right', None) + set_prompt(self, 'PS2', 'left', 'continuation') + set_prompt(self, 'RPS2', 'right', 'continuation') + set_prompt(self, 'PS3', 'left', 'select') + used_powerlines[id(self)] = self + zsh_globals['_powerline'] = self + class Prompt(object): - __slots__ = ('powerline', 'side', 'savedpsvar', 'savedps', 'args', 'theme', 'above') + __slots__ = ('powerline', 'side', 'savedpsvar', 'savedps', 'args', 'theme', 'above', '__weakref__') 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 @@ -143,9 +158,7 @@ class Prompt(object): def __del__(self): if self.savedps: zsh.setvalue(self.savedpsvar, self.savedps) - used_powerlines.remove(self.powerline) - if self.powerline not in used_powerlines: - self.powerline.shutdown() + self.powerline.shutdown() def set_prompt(powerline, psvar, side, theme, above=False): @@ -155,18 +168,18 @@ def set_prompt(powerline, psvar, side, theme, above=False): savedps = None zpyvar = 'ZPYTHON_POWERLINE_' + psvar prompt = Prompt(powerline, side, theme, psvar, savedps, above) + zsh.eval('unset ' + zpyvar) zsh.set_special_string(zpyvar, prompt) zsh.setvalue(psvar, '${' + zpyvar + '}') + return ref(prompt) -def setup(): - powerline = ZshPowerline(Args()) - used_powerlines.append(powerline) - used_powerlines.append(powerline) - set_prompt(powerline, 'PS1', 'left', None, above=True) - set_prompt(powerline, 'RPS1', 'right', None) - set_prompt(powerline, 'PS2', 'left', 'continuation') - set_prompt(powerline, 'RPS2', 'right', 'continuation') - set_prompt(powerline, 'PS3', 'left', 'select') +def reload(): + for powerline in tuple(used_powerlines.values()): + powerline.reload() + + +def setup(zsh_globals): + powerline = ZshPowerline() + powerline.setup(zsh_globals) atexit.register(shutdown) - return powerline diff --git a/powerline/bindings/zsh/powerline.zsh b/powerline/bindings/zsh/powerline.zsh index 8a7078f1..b75f138d 100644 --- a/powerline/bindings/zsh/powerline.zsh +++ b/powerline/bindings/zsh/powerline.zsh @@ -122,8 +122,13 @@ _powerline_setup_prompt() { if test -z "${POWERLINE_NO_ZSH_ZPYTHON}" && { zmodload libzpython || zmodload zsh/zpython } &>/dev/null ; then precmd_functions+=( _powerline_update_counter ) zpython 'from powerline.bindings.zsh import setup as _powerline_setup' - zpython '_powerline = _powerline_setup()' + zpython '_powerline_setup(globals())' zpython 'del _powerline_setup' + powerline-reload() { + zpython 'from powerline.bindings.zsh import reload as _powerline_reload' + zpython '_powerline_reload()' + zpython 'del _powerline_reload' + } else if test -z "${POWERLINE_COMMAND}" ; then POWERLINE_COMMAND=( "$($POWERLINE_CONFIG shell command)" ) diff --git a/powerline/ipython.py b/powerline/ipython.py index 7d675058..f658f111 100644 --- a/powerline/ipython.py +++ b/powerline/ipython.py @@ -22,40 +22,36 @@ class RewriteResult(object): return RewriteResult(self.prompt + s) -class IpythonPowerline(Powerline): - def init(self, is_prompt, old_widths): - super(IpythonPowerline, self).init( +class IPythonPowerline(Powerline): + def init(self): + super(IPythonPowerline, self).init( 'ipython', - renderer_module=('.prompt' if is_prompt else None), use_daemon_threads=True ) - self.old_widths = old_widths - - def create_renderer(self, *args, **kwargs): - super(IpythonPowerline, self).create_renderer(*args, **kwargs) - self.renderer.old_widths = self.old_widths def get_config_paths(self): if self.paths: return self.paths else: - return super(IpythonPowerline, self).get_config_paths() + return super(IPythonPowerline, self).get_config_paths() def get_local_themes(self, local_themes): return dict(((type, {'config': self.load_theme_config(name)}) for type, name in local_themes.items())) def load_main_config(self): - r = super(IpythonPowerline, self).load_main_config() + r = super(IPythonPowerline, self).load_main_config() if self.config_overrides: mergedicts(r, self.config_overrides) return r def load_theme_config(self, name): - r = super(IpythonPowerline, self).load_theme_config(name) + r = super(IPythonPowerline, self).load_theme_config(name) 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) + def do_setup(self, wrefs): + for wref in wrefs: + obj = wref() + if obj is not None: + setattr(obj, 'powerline', self) diff --git a/powerline/renderers/ipython/__init__.py b/powerline/renderers/ipython/__init__.py index 7d7e4c55..1fce961e 100644 --- a/powerline/renderers/ipython/__init__.py +++ b/powerline/renderers/ipython/__init__.py @@ -4,7 +4,7 @@ from powerline.renderers.shell import ShellRenderer from powerline.theme import Theme -class IpythonRenderer(ShellRenderer): +class IPythonRenderer(ShellRenderer): '''Powerline ipython segment renderer.''' def get_segment_info(self, segment_info, mode): r = self.segment_info.copy() @@ -33,8 +33,42 @@ class IpythonRenderer(ShellRenderer): match['theme'].shutdown() def render(self, *args, **kwargs): - # XXX super(ShellRenderer), *not* super(IpythonRenderer) + # XXX super(ShellRenderer), *not* super(IPythonRenderer) return super(ShellRenderer, self).render(*args, **kwargs) -renderer = IpythonRenderer +class IPythonPromptRenderer(IPythonRenderer): + '''Powerline ipython prompt (in and in2) renderer''' + escape_hl_start = '\x01' + escape_hl_end = '\x02' + + +class IPythonNonPromptRenderer(IPythonRenderer): + '''Powerline ipython non-prompt (out and rewrite) renderer''' + pass + + +class RendererProxy(object): + '''Powerline IPython renderer proxy which chooses appropriate renderer + + Instantiates two renderer objects: one will be used for prompts and the + other for non-prompts. + ''' + def __init__(self, **kwargs): + old_widths = {} + self.non_prompt_renderer = IPythonNonPromptRenderer(old_widths=old_widths, **kwargs) + self.prompt_renderer = IPythonPromptRenderer(old_widths=old_widths, **kwargs) + + def render_above_lines(self, *args, **kwargs): + return self.non_prompt_renderer.render_above_lines(*args, **kwargs) + + def render(self, is_prompt, *args, **kwargs): + return (self.prompt_renderer if is_prompt else self.non_prompt_renderer).render( + *args, **kwargs) + + def shutdown(self, *args, **kwargs): + self.prompt_renderer.shutdown(*args, **kwargs) + self.non_prompt_renderer.shutdown(*args, **kwargs) + + +renderer = RendererProxy diff --git a/powerline/renderers/ipython/prompt.py b/powerline/renderers/ipython/prompt.py deleted file mode 100644 index a1103ec8..00000000 --- a/powerline/renderers/ipython/prompt.py +++ /dev/null @@ -1,12 +0,0 @@ -# vim:fileencoding=utf-8:noet - -from powerline.renderers.ipython import IpythonRenderer - - -class IpythonPromptRenderer(IpythonRenderer): - '''Powerline ipython prompt renderer''' - escape_hl_start = '\x01' - escape_hl_end = '\x02' - - -renderer = IpythonPromptRenderer diff --git a/powerline/renderers/shell/__init__.py b/powerline/renderers/shell/__init__.py index fdffaa25..70d9ac6a 100644 --- a/powerline/renderers/shell/__init__.py +++ b/powerline/renderers/shell/__init__.py @@ -24,9 +24,9 @@ class ShellRenderer(Renderer): character_translations = Renderer.character_translations.copy() - def __init__(self, *args, **kwargs): - super(ShellRenderer, self).__init__(*args, **kwargs) - self.old_widths = {} + def __init__(self, old_widths=None, **kwargs): + super(ShellRenderer, self).__init__(**kwargs) + self.old_widths = old_widths if old_widths is not None else {} def render(self, segment_info, **kwargs): local_theme = segment_info.get('local_theme') diff --git a/powerline/shell.py b/powerline/shell.py index cbd55fae..5920f897 100644 --- a/powerline/shell.py +++ b/powerline/shell.py @@ -43,9 +43,8 @@ class ShellPowerline(Powerline): for key, val in local_themes.items() )) - def setup(self, obj): + def do_setup(self, obj): obj.powerline = self - super(ShellPowerline, self).setup(obj) def get_argparser(parser=None, *args, **kwargs): diff --git a/powerline/vim.py b/powerline/vim.py index 3594a6c0..23a59900 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -110,8 +110,7 @@ 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() + def do_setup(self, pyeval=None, pycmd=None, can_replace_pyeval=True): import __main__ if not pyeval: pyeval = 'pyeval' if sys.version_info < (3,) else 'py3eval' diff --git a/tests/test_provided_config_files.py b/tests/test_provided_config_files.py index da160ab1..aeba0617 100644 --- a/tests/test_provided_config_files.py +++ b/tests/test_provided_config_files.py @@ -109,20 +109,20 @@ class TestConfig(TestCase): powerline.render(segment_info={'args': args}) def test_ipython(self): - from powerline.ipython import IpythonPowerline + from powerline.ipython import IPythonPowerline - class IpyPowerline(IpythonPowerline): + class IpyPowerline(IPythonPowerline): paths = None config_overrides = None theme_overrides = {} segment_info = Args(prompt_count=1) - with IpyPowerline(True, {}) as powerline: + with IpyPowerline() as powerline: for prompt_type in ['in', 'in2']: powerline.render(matcher_info=prompt_type, segment_info=segment_info) powerline.render(matcher_info=prompt_type, segment_info=segment_info) - with IpyPowerline(False, {}) as powerline: + with IpyPowerline() as powerline: for prompt_type in ['out', 'rewrite']: powerline.render(matcher_info=prompt_type, segment_info=segment_info) powerline.render(matcher_info=prompt_type, segment_info=segment_info)