Merge pull request #1000 from ZyX-I/non-vim-reload

Add powerline reloading support to IPython and zsh with libzpython
This commit is contained in:
Nikolai Aleksandrovich Pavlov 2014-08-19 23:56:55 +04:00
commit f9e525bd9a
14 changed files with 258 additions and 118 deletions

View File

@ -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 <config-themes-seg-draw_divider>`

View File

@ -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()

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)" )

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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):

View File

@ -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'

View File

@ -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)