diff --git a/README.rst b/README.rst index 4a9f3140..071d1f2f 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ Awesome, i3 and Qtile.** * `Support forum`_ (powerline-support@googlegroups.com) * `Development discussion`_ (powerline-dev@googlegroups.com) -.. image:: https://api.travis-ci.org/powerline/powerline.png?branch=develop +.. image:: https://api.travis-ci.org/powerline/powerline.svg?branch=develop :target: `travis-build-status`_ :alt: Build status @@ -52,7 +52,7 @@ hassle for me / what happened to the original vim-powerline project / …* You should check out some of the Powerline derivatives. The most lightweight and feature-rich alternative is currently Bailey Ling’s `vim-airline -`_ project. +`_ project. ------ @@ -92,4 +92,4 @@ Vim statusline The font in the screenshots is `Pragmata Pro`_ by Fabrizio Schiavi. -.. _`Pragmata Pro`: http://www.fsd.it/fonts/pragmatapro.htm +.. _`Pragmata Pro`: http://www.fsd.it/shop/fonts/pragmatapro diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py index 3d8aea0d..6c1efb15 100644 --- a/powerline/bindings/ipython/post_0_11.py +++ b/powerline/bindings/ipython/post_0_11.py @@ -2,11 +2,19 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) from weakref import ref +from warnings import warn -from IPython.core.prompts import PromptManager +try: + from IPython.core.prompts import PromptManager + has_prompt_manager = True +except ImportError: + has_prompt_manager = False from IPython.core.magic import Magics, magics_class, line_magic -from powerline.ipython import IPythonPowerline, RewriteResult +from powerline.ipython import IPythonPowerline, IPythonInfo + +if has_prompt_manager: + from powerline.ipython import RewriteResult @magics_class @@ -23,41 +31,13 @@ class PowerlineMagics(Magics): raise ValueError('Expected `reload`, but got {0}'.format(line)) -class IPythonInfo(object): - def __init__(self, shell): - self._shell = shell - - @property - def prompt_count(self): - return self._shell.execution_count - - -class PowerlinePromptManager(PromptManager): - 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): - res = self.powerline.render( - is_prompt=name.startswith('in'), - side='left', - output_width=True, - output_raw=not color, - matcher_info=name, - segment_info=self.powerline_segment_info, - ) - self.txtwidth = res[-1] - self.width = res[-1] - ret = res[0] if color else res[1] - if name == 'rewrite': - return RewriteResult(ret) - else: - return ret +old_prompt_manager = None class ShutdownHook(object): - powerline = lambda: None + def __init__(self, ip): + self.powerline = lambda: None + ip.hooks.shutdown_hook.add(self) def __call__(self): from IPython.core.hooks import TryNext @@ -67,40 +47,77 @@ class ShutdownHook(object): 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.config_paths = config.get('config_paths') - super(ConfigurableIPythonPowerline, self).init() +if has_prompt_manager: + class PowerlinePromptManager(PromptManager): + def __init__(self, powerline, shell): + self.powerline = powerline + self.powerline_segment_info = IPythonInfo(shell) + self.shell = shell - 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) + def render(self, name, color=True, *args, **kwargs): + res = self.powerline.render( + is_prompt=name.startswith('in'), + side='left', + output_width=True, + output_raw=not color, + matcher_info=name, + segment_info=self.powerline_segment_info, + ) + self.txtwidth = res[-1] + self.width = res[-1] + ret = res[0] if color else res[1] + if name == 'rewrite': + return RewriteResult(ret) + else: + return ret - ip.prompt_manager = prompt_manager - ip.register_magics(magics) + 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.config_paths = config.get('config_paths') + if has_prompt_manager: + renderer_module = '.pre_5' + else: + renderer_module = '.since_5' + super(ConfigurableIPythonPowerline, self).init( + renderer_module=renderer_module) + def do_setup(self, ip, shutdown_hook): + global old_prompt_manager -old_prompt_manager = None + if old_prompt_manager is None: + old_prompt_manager = ip.prompt_manager + prompt_manager = PowerlinePromptManager( + powerline=self, + shell=ip.prompt_manager.shell, + ) + ip.prompt_manager = prompt_manager + + magics = PowerlineMagics(ip, self) + shutdown_hook.powerline = ref(self) + ip.register_magics(magics) def load_ipython_extension(ip): - global old_prompt_manager - old_prompt_manager = ip.prompt_manager - - powerline = ConfigurableIPythonPowerline(ip) - shutdown_hook = ShutdownHook() - - powerline.setup(ip, shutdown_hook) - - ip.hooks.shutdown_hook.add(shutdown_hook) + if has_prompt_manager: + shutdown_hook = ShutdownHook(ip) + powerline = ConfigurableIPythonPowerline(ip) + powerline.setup(ip, shutdown_hook) + else: + from powerline.bindings.ipython.since_5 import PowerlinePrompts + ip.prompts_class = PowerlinePrompts + ip.prompts = PowerlinePrompts(ip) + warn(DeprecationWarning( + 'post_0_11 extension is deprecated since IPython 5, use\n' + ' from powerline.bindings.ipython.since_5 import PowerlinePrompts\n' + ' c.TerminalInteractiveShell.prompts_class = PowerlinePrompts\n' + )) def unload_ipython_extension(ip): - ip.prompt_manager = old_prompt_manager + global old_prompt_manager + if old_prompt_manager is not None: + ip.prompt_manager = old_prompt_manager + old_prompt_manager = None diff --git a/powerline/bindings/ipython/pre_0_11.py b/powerline/bindings/ipython/pre_0_11.py index fa34cd86..2bd80959 100644 --- a/powerline/bindings/ipython/pre_0_11.py +++ b/powerline/bindings/ipython/pre_0_11.py @@ -99,7 +99,7 @@ class ConfigurableIPythonPowerline(IPythonPowerline): self.config_overrides = config_overrides self.theme_overrides = theme_overrides self.config_paths = config_paths - super(ConfigurableIPythonPowerline, self).init() + super(ConfigurableIPythonPowerline, self).init(renderer_module='.pre_5') def ipython_magic(self, ip, parameter_s=''): if parameter_s == 'reload': diff --git a/powerline/bindings/ipython/since_5.py b/powerline/bindings/ipython/since_5.py new file mode 100644 index 00000000..ab92d6d6 --- /dev/null +++ b/powerline/bindings/ipython/since_5.py @@ -0,0 +1,79 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from weakref import ref + +from IPython.terminal.prompts import Prompts +from pygments.token import Token # NOQA + +from powerline.ipython import IPythonPowerline +from powerline.renderers.ipython.since_5 import PowerlinePromptStyle +from powerline.bindings.ipython.post_0_11 import PowerlineMagics, ShutdownHook + + +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.config_paths = config.get('config_paths') + super(ConfigurableIPythonPowerline, self).init( + renderer_module='.since_5') + + def do_setup(self, ip, prompts, shutdown_hook): + prompts.powerline = self + + saved_msfn = ip._make_style_from_name + + if hasattr(saved_msfn, 'powerline_original'): + saved_msfn = saved_msfn.powerline_original + + def _make_style_from_name(ip, name): + prev_style = saved_msfn(name) + new_style = PowerlinePromptStyle(lambda: prev_style) + return new_style + + _make_style_from_name.powerline_original = saved_msfn + + if not isinstance(ip._style, PowerlinePromptStyle): + prev_style = ip._style + ip._style = PowerlinePromptStyle(lambda: prev_style) + + if not isinstance(saved_msfn, type(self.init)): + _saved_msfn = saved_msfn + saved_msfn = lambda: _saved_msfn(ip) + + ip._make_style_from_name = _make_style_from_name + + magics = PowerlineMagics(ip, self) + ip.register_magics(magics) + + if shutdown_hook: + shutdown_hook.powerline = ref(self) + + +class PowerlinePrompts(Prompts): + '''Class that returns powerline prompts + ''' + def __init__(self, shell): + shutdown_hook = ShutdownHook(shell) + powerline = ConfigurableIPythonPowerline(shell) + self.shell = shell + powerline.do_setup(shell, self, shutdown_hook) + self.last_output_count = None + self.last_output = {} + + for prompt in ('in', 'continuation', 'rewrite', 'out'): + exec(( + 'def {0}_prompt_tokens(self, *args, **kwargs):\n' + ' if self.last_output_count != self.shell.execution_count:\n' + ' self.last_output.clear()\n' + ' self.last_output_count = self.shell.execution_count\n' + ' if "{0}" not in self.last_output:\n' + ' self.last_output["{0}"] = self.powerline.render(' + ' side="left",' + ' matcher_info="{1}",' + ' segment_info=self.shell,' + ' ) + [(Token.Generic.Prompt, " ")]\n' + ' return self.last_output["{0}"]' + ).format(prompt, 'in2' if prompt == 'continuation' else prompt)) diff --git a/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf new file mode 100644 index 00000000..6ee5c8b0 --- /dev/null +++ b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf @@ -0,0 +1,2 @@ +# Starting from tmux-2.1 escaping of dollar signs inside #() is harmful +set -qg status-left "#{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_FG]#[bg=$_POWERLINE_SESSION_PREFIX_BG]#[$_POWERLINE_SESSION_PREFIX_ATTR],#[fg=$_POWERLINE_SESSION_FG]#[bg=$_POWERLINE_SESSION_BG]#[$_POWERLINE_SESSION_ATTR]} #S #{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_BG],#[fg=$_POWERLINE_SESSION_BG]}#[bg=$_POWERLINE_BACKGROUND_BG]#[nobold]$_POWERLINE_LEFT_HARD_DIVIDER#(env $POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS tmux left --width=`tmux display -p '#{client_width}'` -R width_adjust=`tmux show-options -g status-right-length | cut -d' ' -f2` -R pane_id=\"`tmux display -p '#D'`\")" diff --git a/powerline/config_files/colorschemes/default.json b/powerline/config_files/colorschemes/default.json index 61b8cc36..89f17c3c 100644 --- a/powerline/config_files/colorschemes/default.json +++ b/powerline/config_files/colorschemes/default.json @@ -43,6 +43,8 @@ "branch_dirty": { "fg": "brightyellow", "bg": "gray2", "attrs": [] }, "branch_clean": { "fg": "gray9", "bg": "gray2", "attrs": [] }, "branch:divider": { "fg": "gray7", "bg": "gray2", "attrs": [] }, + "stash": "branch_dirty", + "stash:divider": "branch:divider", "cwd": "information:additional", "cwd:current_folder": "information:regular", "cwd:divider": { "fg": "gray7", "bg": "gray4", "attrs": [] }, diff --git a/powerline/config_files/colorschemes/solarized.json b/powerline/config_files/colorschemes/solarized.json index e858e68c..4f11fa47 100644 --- a/powerline/config_files/colorschemes/solarized.json +++ b/powerline/config_files/colorschemes/solarized.json @@ -15,6 +15,7 @@ "branch": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": [] }, "branch_clean": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] }, + "stash": "branch_dirty", "email_alert_gradient": { "fg": "solarized:base3", "bg": "yellow_orange_red", "attrs": [] }, "email_alert": "warning:regular", "cwd": "information:additional", diff --git a/powerline/config_files/colorschemes/vim/solarized.json b/powerline/config_files/colorschemes/vim/solarized.json index bf603851..054edb08 100644 --- a/powerline/config_files/colorschemes/vim/solarized.json +++ b/powerline/config_files/colorschemes/vim/solarized.json @@ -12,6 +12,7 @@ "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base01", "attrs": [] }, "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base01", "attrs": [] }, "branch:divider": { "fg": "solarized:base1", "bg": "solarized:base01", "attrs": [] }, + "stash:divider": "branch:divider", "file_name": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] }, "window_title": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] }, "file_name_no_file": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] }, diff --git a/powerline/config_files/colorschemes/vim/solarizedlight.json b/powerline/config_files/colorschemes/vim/solarizedlight.json index 2dcbb23d..928b8a50 100644 --- a/powerline/config_files/colorschemes/vim/solarizedlight.json +++ b/powerline/config_files/colorschemes/vim/solarizedlight.json @@ -12,6 +12,8 @@ "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] }, "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": [] }, "branch:divider": { "fg": "solarized:base1", "bg": "solarized:base2", "attrs": [] }, + "stash": "branch_dirty", + "stash:divider": "branch:divider", "file_name": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] }, "window_title": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, "file_size": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] }, diff --git a/powerline/config_files/themes/ascii.json b/powerline/config_files/themes/ascii.json index 7b12d446..c6760613 100644 --- a/powerline/config_files/themes/ascii.json +++ b/powerline/config_files/themes/ascii.json @@ -15,6 +15,9 @@ "branch": { "before": "BR " }, + "stash": { + "before": "ST " + }, "cwd": { "args": { "ellipsis": "..." diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json index 4ca9f0ee..db8246b7 100644 --- a/powerline/config_files/themes/powerline.json +++ b/powerline/config_files/themes/powerline.json @@ -14,6 +14,9 @@ "branch": { "before": " " }, + "stash": { + "before": "⌆ " + }, "cwd": { "args": { "ellipsis": "⋯" diff --git a/powerline/config_files/themes/powerline_unicode7.json b/powerline/config_files/themes/powerline_unicode7.json index d470d3a9..0d3c5d13 100644 --- a/powerline/config_files/themes/powerline_unicode7.json +++ b/powerline/config_files/themes/powerline_unicode7.json @@ -14,6 +14,9 @@ "branch": { "before": "🔀 " }, + "stash": { + "before": "📝" + }, "cwd": { "args": { "ellipsis": "⋯" diff --git a/powerline/config_files/themes/shell/default.json b/powerline/config_files/themes/shell/default.json index 480da5c9..38039d80 100644 --- a/powerline/config_files/themes/shell/default.json +++ b/powerline/config_files/themes/shell/default.json @@ -30,6 +30,10 @@ "function": "powerline.segments.shell.last_pipe_status", "priority": 10 }, + { + "function": "powerline.segments.common.vcs.stash", + "priority": 50 + }, { "function": "powerline.segments.common.vcs.branch", "priority": 40 diff --git a/powerline/config_files/themes/unicode.json b/powerline/config_files/themes/unicode.json index 4b52fd3b..049dcf08 100644 --- a/powerline/config_files/themes/unicode.json +++ b/powerline/config_files/themes/unicode.json @@ -14,6 +14,9 @@ "branch": { "before": "⎇ " }, + "stash": { + "before": "⌆" + }, "cwd": { "args": { "ellipsis": "⋯" diff --git a/powerline/config_files/themes/unicode_terminus.json b/powerline/config_files/themes/unicode_terminus.json index b7b005e4..47320529 100644 --- a/powerline/config_files/themes/unicode_terminus.json +++ b/powerline/config_files/themes/unicode_terminus.json @@ -14,6 +14,9 @@ "branch": { "before": "BR " }, + "stash": { + "before": "ST " + }, "cwd": { "args": { "ellipsis": "…" diff --git a/powerline/config_files/themes/unicode_terminus_condensed.json b/powerline/config_files/themes/unicode_terminus_condensed.json index fc9e90aa..bac59be2 100644 --- a/powerline/config_files/themes/unicode_terminus_condensed.json +++ b/powerline/config_files/themes/unicode_terminus_condensed.json @@ -14,6 +14,9 @@ "branch": { "before": "B " }, + "stash": { + "before": "S " + }, "cwd": { "args": { "use_path_separator": true, diff --git a/powerline/ipython.py b/powerline/ipython.py index 6e22c41b..cb84fc7e 100644 --- a/powerline/ipython.py +++ b/powerline/ipython.py @@ -6,6 +6,15 @@ from powerline.lib.dict import mergedicts from powerline.lib.unicode import string +class IPythonInfo(object): + def __init__(self, shell): + self._shell = shell + + @property + def prompt_count(self): + return self._shell.execution_count + + # HACK: ipython tries to only leave us with plain ASCII class RewriteResult(object): def __init__(self, prompt): diff --git a/powerline/lib/vcs/git.py b/powerline/lib/vcs/git.py index c1d77a61..9bdf56e4 100644 --- a/powerline/lib/vcs/git.py +++ b/powerline/lib/vcs/git.py @@ -102,6 +102,13 @@ try: def ignore_event(path, name): return False + def stash(self): + try: + stashref = git.Repository(git_directory(self.directory)).lookup_reference('refs/stash') + except KeyError: + return 0 + return sum(1 for _ in stashref.log()) + def do_status(self, directory, path): if path: try: @@ -171,6 +178,9 @@ except ImportError: def _gitcmd(self, directory, *args): return readlines(('git',) + args, directory) + def stash(self): + return sum(1 for _ in self._gitcmd(self.directory, 'stash', 'list')) + def do_status(self, directory, path): if path: try: diff --git a/powerline/renderer.py b/powerline/renderer.py index 76a7e796..147751a8 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -4,6 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct import sys import os import re +import operator from itertools import chain @@ -310,6 +311,19 @@ class Renderer(object): }, } + hl_join = staticmethod(''.join) + '''Join a list of rendered segments into a resulting string + + This method exists to deal with non-string render outputs, so `segments` + may actually be not an iterable with strings. + + :param list segments: + Iterable containing rendered segments. By “rendered segments” + :py:meth:`Renderer.hl` output is meant. + + :return: Results of joining these segments. + ''' + def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme): '''Like Renderer.render(), but accept theme in place of matcher_info ''' @@ -323,7 +337,7 @@ class Renderer(object): # No width specified, so we don’t need to crop or pad anything if output_width: current_width = self._render_length(theme, segments, self.compute_divider_widths(theme)) - return construct_returned_value(''.join([ + return construct_returned_value(self.hl_join([ segment['_rendered_hl'] for segment in self._render_segments(theme, segments) ]) + self.hlstyle(), segments, current_width, output_raw, output_width) @@ -378,7 +392,10 @@ class Renderer(object): elif output_width: current_width = self._render_length(theme, segments, divider_widths) - rendered_highlighted = ''.join([segment['_rendered_hl'] for segment in self._render_segments(theme, segments)]) + rendered_highlighted = self.hl_join([ + segment['_rendered_hl'] + for segment in self._render_segments(theme, segments) + ]) if rendered_highlighted: rendered_highlighted += self.hlstyle() diff --git a/powerline/renderers/ipython/__init__.py b/powerline/renderers/ipython/__init__.py index a84418f1..8f463b52 100644 --- a/powerline/renderers/ipython/__init__.py +++ b/powerline/renderers/ipython/__init__.py @@ -1,12 +1,11 @@ # vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) -from powerline.renderers.shell import ShellRenderer -from powerline.renderers.shell.readline import ReadlineRenderer from powerline.theme import Theme +from powerline.renderers.shell import PromptRenderer -class IPythonRenderer(ShellRenderer): +class IPythonRenderer(PromptRenderer): '''Powerline ipython segment renderer.''' def get_segment_info(self, segment_info, mode): r = self.segment_info.copy() @@ -33,50 +32,3 @@ class IPythonRenderer(ShellRenderer): for match in self.local_themes.values(): if 'theme' in match: match['theme'].shutdown() - - def render(self, **kwargs): - # XXX super(ShellRenderer), *not* super(IPythonRenderer) - return super(ShellRenderer, self).render(**kwargs) - - def do_render(self, segment_info, **kwargs): - segment_info.update(client_id='ipython') - return super(IPythonRenderer, self).do_render( - segment_info=segment_info, - **kwargs - ) - - -class IPythonPromptRenderer(IPythonRenderer, ReadlineRenderer): - '''Powerline ipython prompt (in and in2) renderer''' - pass - - -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/pre_5.py b/powerline/renderers/ipython/pre_5.py new file mode 100644 index 00000000..9fc8c211 --- /dev/null +++ b/powerline/renderers/ipython/pre_5.py @@ -0,0 +1,56 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +from powerline.renderers.shell import ShellRenderer +from powerline.renderers.shell.readline import ReadlineRenderer +from powerline.renderers.ipython import IPythonRenderer + + +class IPythonPre50Renderer(IPythonRenderer, ShellRenderer): + '''Powerline ipython segment renderer for pre-5.0 IPython versions.''' + def render(self, **kwargs): + # XXX super(ShellRenderer), *not* super(IPythonPre50Renderer) + return super(ShellRenderer, self).render(**kwargs) + + def do_render(self, segment_info, **kwargs): + segment_info.update(client_id='ipython') + return super(IPythonPre50Renderer, self).do_render( + segment_info=segment_info, + **kwargs + ) + + +class IPythonPromptRenderer(IPythonPre50Renderer, ReadlineRenderer): + '''Powerline ipython prompt (in and in2) renderer''' + pass + + +class IPythonNonPromptRenderer(IPythonPre50Renderer): + '''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/since_5.py b/powerline/renderers/ipython/since_5.py new file mode 100644 index 00000000..8a26da72 --- /dev/null +++ b/powerline/renderers/ipython/since_5.py @@ -0,0 +1,130 @@ +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import operator + +from collections import defaultdict + +try: + from __builtin__ import reduce +except ImportError: + from functools import reduce + +from pygments.token import Token +from prompt_toolkit.styles import DynamicStyle, Attrs + +from powerline.renderers.ipython import IPythonRenderer +from powerline.ipython import IPythonInfo +from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE + + +PowerlinePromptToken = Token.Generic.Prompt.Powerline + + +# Note: since 2.7 there is dict.__missing__ with same purpose. But in 2.6 one +# must use defaultdict to get __missing__ working. +class PowerlineStyleDict(defaultdict): + '''Dictionary used for getting pygments style for Powerline groups + ''' + def __new__(cls, missing_func): + return defaultdict.__new__(cls) + + def __init__(self, missing_func): + super(PowerlineStyleDict, self).__init__() + self.missing_func = missing_func + + def __missing__(self, key): + return self.missing_func(key) + + +class PowerlinePromptStyle(DynamicStyle): + def get_attrs_for_token(self, token): + if ( + token not in PowerlinePromptToken + or len(token) != len(PowerlinePromptToken) + 1 + or not token[-1].startswith('Pl') + or token[-1] == 'Pl' + ): + return super(PowerlinePromptStyle, self).get_attrs_for_token(token) + ret = { + 'color': None, + 'bgcolor': None, + 'bold': None, + 'underline': None, + 'italic': None, + 'reverse': False, + 'blink': False, + } + for prop in token[-1][3:].split('_'): + if prop[0] == 'a': + ret[prop[1:]] = True + elif prop[0] == 'f': + ret['color'] = prop[1:] + elif prop[0] == 'b': + ret['bgcolor'] = prop[1:] + return Attrs(**ret) + + def get_token_to_attributes_dict(self): + dct = super(PowerlinePromptStyle, self).get_token_to_attributes_dict() + + def fallback(key): + try: + return dct[key] + except KeyError: + return self.get_attrs_for_token(key) + + return PowerlineStyleDict(fallback) + + def invalidation_hash(self): + return super(PowerlinePromptStyle, self).invalidation_hash() + 1 + + +class IPythonPygmentsRenderer(IPythonRenderer): + reduce_initial = [] + + def get_segment_info(self, segment_info, mode): + return super(IPythonPygmentsRenderer, self).get_segment_info( + IPythonInfo(segment_info), mode) + + @staticmethod + def hl_join(segments): + return reduce(operator.iadd, segments, []) + + def hl(self, contents, fg=None, bg=None, attrs=None): + '''Output highlighted chunk. + + This implementation outputs a list containing a single pair + (:py:class:`pygments.token.Token`, + :py:class:`powerline.lib.unicode.unicode`). + ''' + guifg = None + guibg = None + attrs = [] + if fg is not None and fg is not False: + guifg = fg[1] + if bg is not None and bg is not False: + guibg = bg[1] + if attrs: + attrs = [] + if attrs & ATTR_BOLD: + attrs.append('bold') + if attrs & ATTR_ITALIC: + attrs.append('italic') + if attrs & ATTR_UNDERLINE: + attrs.append('underline') + name = ( + 'Pl' + + ''.join(('_a' + attr for attr in attrs)) + + (('_f%6x' % guifg) if guifg is not None else '') + + (('_b%6x' % guibg) if guibg is not None else '') + ) + return [(getattr(Token.Generic.Prompt.Powerline, name), contents)] + + def hlstyle(self, **kwargs): + return [] + + def get_client_id(self, segment_info): + return id(self) + + +renderer = IPythonPygmentsRenderer diff --git a/powerline/renderers/shell/__init__.py b/powerline/renderers/shell/__init__.py index 8b8b5c85..ebb05019 100644 --- a/powerline/renderers/shell/__init__.py +++ b/powerline/renderers/shell/__init__.py @@ -13,41 +13,30 @@ def int_to_rgb(num): return r, g, b -class ShellRenderer(Renderer): - '''Powerline shell segment renderer.''' - escape_hl_start = '' - escape_hl_end = '' - term_truecolor = False - term_escape_style = 'auto' - tmux_escape = False - screen_escape = False - - character_translations = Renderer.character_translations.copy() +class PromptRenderer(Renderer): + '''Powerline generic prompt segment renderer''' def __init__(self, old_widths=None, **kwargs): - super(ShellRenderer, self).__init__(**kwargs) + super(PromptRenderer, 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') - return super(ShellRenderer, self).render( - matcher_info=local_theme, - segment_info=segment_info, - **kwargs - ) + def get_client_id(self, segment_info): + '''Get client ID given segment info + + This is used by daemon to correctly cache widths for different clients + using a single renderer instance. + + :param dict segment_info: + :ref:`Segment info dictionary `. Out of it only + ``client_id`` key is used. It is OK for this dictionary to not + contain this key. + + :return: Any hashable value or ``None``. + ''' + return segment_info.get('client_id') if isinstance(segment_info, dict) else None def do_render(self, output_width, segment_info, side, theme, width=None, **kwargs): - if self.term_escape_style == 'auto': - if segment_info['environ'].get('TERM') == 'fbterm': - self.used_term_escape_style = 'fbterm' - else: - self.used_term_escape_style = 'xterm' - else: - self.used_term_escape_style = self.term_escape_style - if isinstance(segment_info, dict): - client_id = segment_info.get('client_id') - else: - client_id = None + client_id = self.get_client_id(segment_info) if client_id is not None: local_key = (client_id, side, None if theme is self.theme else id(theme)) key = (client_id, side, None) @@ -70,7 +59,7 @@ class ShellRenderer(Renderer): width -= self.old_widths[(client_id, 'left', local_key[-1])] except KeyError: pass - res = super(ShellRenderer, self).do_render( + res = super(PromptRenderer, self).do_render( output_width=True, width=width, theme=theme, @@ -86,6 +75,36 @@ class ShellRenderer(Renderer): else: return ret + +class ShellRenderer(PromptRenderer): + '''Powerline shell segment renderer.''' + escape_hl_start = '' + escape_hl_end = '' + term_truecolor = False + term_escape_style = 'auto' + tmux_escape = False + screen_escape = False + + character_translations = Renderer.character_translations.copy() + + def render(self, segment_info, **kwargs): + local_theme = segment_info.get('local_theme') + return super(ShellRenderer, self).render( + matcher_info=local_theme, + segment_info=segment_info, + **kwargs + ) + + def do_render(self, segment_info, **kwargs): + if self.term_escape_style == 'auto': + if segment_info['environ'].get('TERM') == 'fbterm': + self.used_term_escape_style = 'fbterm' + else: + self.used_term_escape_style = 'xterm' + else: + self.used_term_escape_style = self.term_escape_style + return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs) + def hlstyle(self, fg=None, bg=None, attrs=None): '''Highlight a segment. diff --git a/powerline/segments/common/vcs.py b/powerline/segments/common/vcs.py index 90e4e772..1aa5d110 100644 --- a/powerline/segments/common/vcs.py +++ b/powerline/segments/common/vcs.py @@ -56,3 +56,34 @@ branch = with_docstring(BranchSegment(), Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. ''') + + +@requires_filesystem_watcher +@requires_segment_info +class StashSegment(Segment): + divider_highlight_group = None + + @staticmethod + def get_directory(segment_info): + return segment_info['getcwd']() + + def __call__(self, pl, segment_info, create_watcher): + name = self.get_directory(segment_info) + if name: + repo = guess(path=name, create_watcher=create_watcher) + if repo is not None: + stash = getattr(repo, 'stash', None) + if stash: + stashes = stash() + if stashes: + return [{ + 'contents': str(stashes), + 'highlight_groups': ['stash'], + 'divider_highlight_group': self.divider_highlight_group + }] + +stash = with_docstring(StashSegment(), +'''Return the number of current VCS stash entries, if any. + +Highlight groups used: ``stash``. +''') diff --git a/powerline/segments/common/wthr.py b/powerline/segments/common/wthr.py index 30a36a4d..1c6d0809 100644 --- a/powerline/segments/common/wthr.py +++ b/powerline/segments/common/wthr.py @@ -8,7 +8,7 @@ from powerline.lib.threaded import KwThreadedSegment from powerline.segments import with_docstring -# XXX Warning: module name must not be equal to the segment name as long as this +# XXX Warning: module name must not be equal to the segment name as long as this # segment is imported into powerline.segments.common module. @@ -115,19 +115,20 @@ class WeatherSegment(KwThreadedSegment): return self.location_urls[location_query] except KeyError: if location_query is None: - location_data = json.loads(urllib_read('http://freegeoip.net/json/')) + location_data = json.loads(urllib_read('http://geoip.nekudo.com/api/')) location = ','.join(( location_data['city'], - location_data['region_name'], - location_data['country_code'] + location_data['country']['name'], + location_data['country']['code'] )) - self.info('Location returned by freegeoip is {0}', location) + self.info('Location returned by nekudo is {0}', location) else: location = location_query query_data = { 'q': 'use "https://raw.githubusercontent.com/yql/yql-tables/master/weather/weather.bylocation.xml" as we;' - 'select * from we where location="{0}" and unit="c"'.format(location).encode('utf-8'), + 'select * from weather.forecast where woeid in' + ' (select woeid from geo.places(1) where text="{0}") and u="c"'.format(location).encode('utf-8'), 'format': 'json', } self.location_urls[location_query] = url = ( @@ -143,7 +144,7 @@ class WeatherSegment(KwThreadedSegment): response = json.loads(raw_response) try: - condition = response['query']['results']['weather']['rss']['channel']['item']['condition'] + condition = response['query']['results']['channel']['item']['condition'] condition_code = int(condition['code']) temp = float(condition['temp']) except (KeyError, ValueError): @@ -203,7 +204,7 @@ class WeatherSegment(KwThreadedSegment): weather = with_docstring(WeatherSegment(), '''Return weather from Yahoo! Weather. -Uses GeoIP lookup from http://freegeoip.net/ to automatically determine +Uses GeoIP lookup from http://geoip.nekudo.com to automatically determine your current location. This should be changed if you’re in a VPN or if your IP address is registered at another location. diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py index 8c740c95..122c9e9a 100644 --- a/powerline/segments/vim/__init__.py +++ b/powerline/segments/vim/__init__.py @@ -22,7 +22,7 @@ from powerline.lib import add_divider_highlight_group from powerline.lib.vcs import guess from powerline.lib.humanize_bytes import humanize_bytes from powerline.lib import wraps_saveargs as wraps -from powerline.segments.common.vcs import BranchSegment +from powerline.segments.common.vcs import BranchSegment, StashSegment from powerline.segments import with_docstring from powerline.lib.unicode import string, unicode @@ -510,6 +510,25 @@ Divider highlight group used: ``branch:divider``. ''') +@requires_filesystem_watcher +@requires_segment_info +class VimStashSegment(StashSegment): + divider_highlight_group = 'stash:divider' + + @staticmethod + def get_directory(segment_info): + if vim_getbufoption(segment_info, 'buftype'): + return None + return buffer_name(segment_info) + + +stash = with_docstring(VimStashSegment(), +'''Return the number of stashes in the current working branch. + +Highlight groups used: ``stash``. +''') + + @requires_filesystem_watcher @requires_segment_info def file_vcs_status(pl, segment_info, create_watcher): @@ -559,7 +578,7 @@ def trailing_whitespace(pl, segment_info): else: buf = segment_info['buffer'] bws = b' \t' - sws = str(bws) + sws = str(' \t') # Ignore unicode_literals and use native str. for i in range(len(buf)): try: line = buf[i] diff --git a/scripts/powerline-daemon b/scripts/powerline-daemon index 10635c97..c5dba47b 100755 --- a/scripts/powerline-daemon +++ b/scripts/powerline-daemon @@ -231,7 +231,7 @@ def do_one(sock, read_sockets, write_sockets, result_map): def main_loop(sock): - sock.listen(1) + sock.listen(128) sock.setblocking(0) read_sockets, write_sockets = set(), set() diff --git a/setup.py b/setup.py index b8818a87..3ac0c607 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ else: def get_version(): - base_version = '2.4' + base_version = '2.5' base_version += '.dev9999' try: return base_version + '+git.' + str(subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()) @@ -70,7 +70,7 @@ def get_version(): setup( name='powerline-status', - version='2.4', + version='2.5', description='The ultimate statusline/prompt utility.', long_description=README, classifiers=[ diff --git a/tests/common.sh b/tests/common.sh index 14da0e19..eb3bbab7 100644 --- a/tests/common.sh +++ b/tests/common.sh @@ -24,12 +24,19 @@ exit_suite() { } fail() { + local allow_failure= + if test "x$1" = "x--allow-failure" ; then + shift + allow_failure=A + fi local test_name="$1" - local fail_char="$2" + local fail_char="$allow_failure$2" local message="$3" local full_msg="$fail_char $POWERLINE_CURRENT_SUITE|$test_name :: $message" FAIL_SUMMARY="${FAIL_SUMMARY}${NL}${full_msg}" echo "Failed: $full_msg" echo "$full_msg" >> tests/failures - FAILED=1 + if test "x$allow_failure" = "x" ; then + FAILED=1 + fi } diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index 38287c82..9a18faf2 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -44,13 +44,13 @@ def urllib_read(query_url): return '127.0.0.1' elif query_url.startswith('http://ipv4.icanhazip.com'): return '2001:4801:7818:6:abc5:ba2c:ff10:275f' - elif query_url.startswith('http://freegeoip.net/json/'): - return '{"city": "Meppen", "region_code": "06", "region_name": "Niedersachsen", "areacode": "", "ip": "82.145.55.16", "zipcode": "49716", "longitude": 7.3167, "country_name": "Germany", "country_code": "DE", "metrocode": "", "latitude": 52.6833}' + elif query_url.startswith('http://geoip.nekudo.com/api/'): + return '{"city":"Meppen","country":{"name":"Germany", "code":"DE"},"location":{"accuracy_radius":100,"latitude":52.6833,"longitude":7.3167,"time_zone":"Europe\/Berlin"},"ip":"82.145.55.16"}' elif query_url.startswith('http://query.yahooapis.com/v1/public/'): if 'Meppen' in query_url: - return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"-9","text":"Partly Cloudy"},"description":"
\nCurrent Conditions:
\nPartly Cloudy, -9 C
\n
Forecast:
\nSat - Partly Cloudy. High: -9 Low: -19
\nSun - Partly Cloudy. High: -12 Low: -18
\n
\nFull Forecast at Yahoo! Weather

\n(provided by The Weather Channel)
","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}' + return r'{"query":{"count":1,"created":"2016-05-13T19:43:18Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Meppen, NI, DE","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","description":"Yahoo! Weather for Meppen, NI, DE","language":"en-us","lastBuildDate":"Fri, 13 May 2016 09:43 PM CEST","ttl":"60","location":{"city":"Meppen","country":"Germany","region":" NI"},"wind":{"chill":"55","direction":"350","speed":"25"},"atmosphere":{"humidity":"57","pressure":"1004.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"5:35 am","sunset":"9:21 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Meppen, NI, DE at 08:00 PM CEST","lat":"52.68993","long":"7.29115","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","pubDate":"Fri, 13 May 2016 08:00 PM CEST","condition":{"code":"23","date":"Fri, 13 May 2016 08:00 PM CEST","temp":"14","text":"Breezy"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"71","low":"48","text":"Partly Cloudy"},{"code":"28","date":"14 May 2016","day":"Sat","high":"54","low":"44","text":"Mostly Cloudy"},{"code":"11","date":"15 May 2016","day":"Sun","high":"55","low":"43","text":"Showers"},{"code":"28","date":"16 May 2016","day":"Mon","high":"54","low":"42","text":"Mostly Cloudy"},{"code":"28","date":"17 May 2016","day":"Tue","high":"57","low":"43","text":"Mostly Cloudy"},{"code":"12","date":"18 May 2016","day":"Wed","high":"62","low":"45","text":"Rain"},{"code":"28","date":"19 May 2016","day":"Thu","high":"63","low":"48","text":"Mostly Cloudy"},{"code":"28","date":"20 May 2016","day":"Fri","high":"67","low":"50","text":"Mostly Cloudy"},{"code":"30","date":"21 May 2016","day":"Sat","high":"71","low":"50","text":"Partly Cloudy"},{"code":"30","date":"22 May 2016","day":"Sun","high":"74","low":"54","text":"Partly Cloudy"}],"description":"\n
\nCurrent Conditions:\n
Breezy\n
\n
\nForecast:\n
Fri - Partly Cloudy. High: 71Low: 48\n
Sat - Mostly Cloudy. High: 54Low: 44\n
Sun - Showers. High: 55Low: 43\n
Mon - Mostly Cloudy. High: 54Low: 42\n
Tue - Mostly Cloudy. High: 57Low: 43\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n(provided by The Weather Channel)\n
\n]]>","guid":{"isPermaLink":"false"}}}}}}' elif 'Moscow' in query_url: - return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"19","text":"Partly Cloudy"},"description":"
\nCurrent Conditions:
\nPartly Cloudy, -9 C
\n
Forecast:
\nSat - Partly Cloudy. High: -9 Low: -19
\nSun - Partly Cloudy. High: -12 Low: -18
\n
\nFull Forecast at Yahoo! Weather

\n(provided by The Weather Channel)
","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}' + return r'{"query":{"count":1,"created":"2016-05-13T19:47:01Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Moscow, Moscow Federal City, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","description":"Yahoo! Weather for Moscow, Moscow Federal City, RU","language":"en-us","lastBuildDate":"Fri, 13 May 2016 10:47 PM MSK","ttl":"60","location":{"city":"Moscow","country":"Russia","region":" Moscow Federal City"},"wind":{"chill":"45","direction":"80","speed":"11"},"atmosphere":{"humidity":"52","pressure":"993.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"4:19 am","sunset":"8:34 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Moscow, Moscow Federal City, RU at 09:00 PM MSK","lat":"55.741638","long":"37.605061","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","pubDate":"Fri, 13 May 2016 09:00 PM MSK","condition":{"code":"33","date":"Fri, 13 May 2016 09:00 PM MSK","temp":"9","text":"Mostly Clear"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"62","low":"41","text":"Partly Cloudy"},{"code":"30","date":"14 May 2016","day":"Sat","high":"64","low":"43","text":"Partly Cloudy"},{"code":"30","date":"15 May 2016","day":"Sun","high":"63","low":"44","text":"Partly Cloudy"},{"code":"12","date":"16 May 2016","day":"Mon","high":"60","low":"47","text":"Rain"},{"code":"12","date":"17 May 2016","day":"Tue","high":"64","low":"48","text":"Rain"},{"code":"28","date":"18 May 2016","day":"Wed","high":"67","low":"48","text":"Mostly Cloudy"},{"code":"12","date":"19 May 2016","day":"Thu","high":"68","low":"49","text":"Rain"},{"code":"39","date":"20 May 2016","day":"Fri","high":"66","low":"50","text":"Scattered Showers"},{"code":"39","date":"21 May 2016","day":"Sat","high":"69","low":"49","text":"Scattered Showers"},{"code":"30","date":"22 May 2016","day":"Sun","high":"73","low":"50","text":"Partly Cloudy"}],"description":"\n
\nCurrent Conditions:\n
Mostly Clear\n
\n
\nForecast:\n
Fri - Partly Cloudy. High: 62Low: 41\n
Sat - Partly Cloudy. High: 64Low: 43\n
Sun - Partly Cloudy. High: 63Low: 44\n
Mon - Rain. High: 60Low: 47\n
Tue - Rain. High: 64Low: 48\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n(provided by The Weather Channel)\n
\n]]>","guid":{"isPermaLink":"false"}}}}}}' else: raise NotImplementedError diff --git a/tests/test_lib.py b/tests/test_lib.py index 6c7aac25..799fc272 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -566,6 +566,32 @@ class TestVCS(TestCase): self.do_branch_rename_test(repo, lambda b: re.match(r'^[a-f0-9]+$', b)) finally: call(['git', 'checkout', '-q', 'master'], cwd=GIT_REPO) + # Test stashing + self.assertEqual(repo.stash(), 0) + + def stash_save(): + with open(os.path.join(GIT_REPO, 'file'), 'w') as f: + f.write('abc') + return call(['git', 'stash', '-u'], cwd=GIT_REPO, stdout=PIPE) + + def stash_drop(): + return call(['git', 'stash', 'drop'], cwd=GIT_REPO, stdout=PIPE) + + def stash_list(): + return call(['git', 'stash', 'list'], cwd=GIT_REPO, stdout=PIPE) + + try: + stash_save() + self.assertEqual(repo.stash(), 1) + stash_save() + self.assertEqual(repo.stash(), 2) + stash_drop() + self.assertEqual(repo.stash(), 1) + stash_drop() + self.assertEqual(repo.stash(), 0) + finally: + while stash_list(): + stash_drop() def test_git_sym(self): create_watcher = get_fallback_create_watcher() diff --git a/tests/test_provided_config_files.py b/tests/test_provided_config_files.py index 00eec49c..3ea9a7e8 100644 --- a/tests/test_provided_config_files.py +++ b/tests/test_provided_config_files.py @@ -154,11 +154,11 @@ class TestConfig(TestCase): segment_info = Args(prompt_count=1) - with IpyPowerline(logger=get_logger()) as powerline: + with IpyPowerline(logger=get_logger(), renderer_module='.pre_5') as powerline: for prompt_type in ['in', 'in2']: powerline.render(is_prompt=True, matcher_info=prompt_type, segment_info=segment_info) powerline.render(is_prompt=True, matcher_info=prompt_type, segment_info=segment_info) - with IpyPowerline(logger=get_logger()) as powerline: + with IpyPowerline(logger=get_logger(), renderer_module='.pre_5') as powerline: for prompt_type in ['out', 'rewrite']: powerline.render(is_prompt=False, matcher_info=prompt_type, segment_info=segment_info) powerline.render(is_prompt=False, matcher_info=prompt_type, segment_info=segment_info) diff --git a/tests/test_segments.py b/tests/test_segments.py index 0a7adcbf..05484fe1 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -738,6 +738,29 @@ class TestVcs(TestCommon): 'divider_highlight_group': None }]) + def test_stash(self): + pl = Pl() + create_watcher = get_fallback_create_watcher() + stash = partial(self.module.stash, pl=pl, create_watcher=create_watcher, segment_info={'getcwd': os.getcwd}) + + def forge_stash(n): + return replace_attr(self.module, 'guess', get_dummy_guess(stash=lambda: n, directory='/tmp/tests')) + + with forge_stash(0): + self.assertEqual(stash(), None) + with forge_stash(1): + self.assertEqual(stash(), [{ + 'highlight_groups': ['stash'], + 'contents': '1', + 'divider_highlight_group': None + }]) + with forge_stash(2): + self.assertEqual(stash(), [{ + 'highlight_groups': ['stash'], + 'contents': '2', + 'divider_highlight_group': None + }]) + class TestTime(TestCommon): module_name = 'time' @@ -844,46 +867,46 @@ class TestWthr(TestCommon): pl = Pl() with replace_attr(self.module, 'urllib_read', urllib_read): self.assertEqual(self.module.weather(pl=pl), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} ]) self.assertEqual(self.module.weather(pl=pl, temp_coldest=0, temp_hottest=100), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 14.0} ]) self.assertEqual(self.module.weather(pl=pl, temp_coldest=-100, temp_hottest=-50), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 100} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 100} ]) - self.assertEqual(self.module.weather(pl=pl, icons={'cloudy': 'o'}), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'o '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} + self.assertEqual(self.module.weather(pl=pl, icons={'blustery': 'o'}), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'o '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} ]) - self.assertEqual(self.module.weather(pl=pl, icons={'partly_cloudy_day': 'x'}), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'x '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} + self.assertEqual(self.module.weather(pl=pl, icons={'windy': 'x'}), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'x '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} ]) self.assertEqual(self.module.weather(pl=pl, unit='F'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '16°F', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '57°F', 'gradient_level': 62.857142857142854} ]) self.assertEqual(self.module.weather(pl=pl, unit='K'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '264K', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '287K', 'gradient_level': 62.857142857142854} ]) self.assertEqual(self.module.weather(pl=pl, temp_format='{temp:.1e}C'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9.0e+00C', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '1.4e+01C', 'gradient_level': 62.857142857142854} ]) with replace_attr(self.module, 'urllib_read', urllib_read): self.module.weather.startup(pl=pl, location_query='Meppen,06,DE') self.assertEqual(self.module.weather(pl=pl), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} ]) self.assertEqual(self.module.weather(pl=pl, location_query='Moscow,RU'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'CLOUDS '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '19°C', 'gradient_level': 70.0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_fair_night', 'weather_condition_night', 'weather_conditions', 'weather'], 'contents': 'NIGHT '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '9°C', 'gradient_level': 55.714285714285715} ]) self.module.weather.shutdown() @@ -980,7 +1003,7 @@ class TestI3WM(TestCase): class Conn(object): def get_tree(self): return self - + def descendents(self): nodes_unfocused = [Args(focused = False)] nodes_focused = [Args(focused = True)] @@ -1390,6 +1413,30 @@ class TestVim(TestCase): {'divider_highlight_group': 'branch:divider', 'highlight_groups': ['branch_clean', 'branch'], 'contents': 'foo'} ]) + def test_stash(self): + pl = Pl() + create_watcher = get_fallback_create_watcher() + with vim_module._with('buffer', '/foo') as segment_info: + stash = partial(self.vim.stash, pl=pl, create_watcher=create_watcher, segment_info=segment_info) + + def forge_stash(n): + return replace_attr(self.vcs, 'guess', get_dummy_guess(stash=lambda: n)) + + with forge_stash(0): + self.assertEqual(stash(), None) + with forge_stash(1): + self.assertEqual(stash(), [{ + 'divider_highlight_group': 'stash:divider', + 'highlight_groups': ['stash'], + 'contents': '1' + }]) + with forge_stash(2): + self.assertEqual(stash(), [{ + 'divider_highlight_group': 'stash:divider', + 'highlight_groups': ['stash'], + 'contents': '2' + }]) + def test_file_vcs_status(self): pl = Pl() create_watcher = get_fallback_create_watcher() diff --git a/tests/test_shells/test.sh b/tests/test_shells/test.sh index 8abc8532..40ab938a 100755 --- a/tests/test_shells/test.sh +++ b/tests/test_shells/test.sh @@ -88,6 +88,10 @@ do_run_test() { || test "$PYTHON_IMPLEMENTATION" = "PyPy" \ ) \ ) \ + || ( \ + test "x${SH}" = "xipython" \ + && test "$("${PYTHON}" -mIPython --version | head -n1 | cut -d. -f1)" -ge 5 \ + ) \ ) ; then wait_for_echo_arg="--wait-for-echo" fi @@ -363,13 +367,6 @@ if test -z "${ONLY_SHELL}" || test "x${ONLY_SHELL%sh}" != "x${ONLY_SHELL}" || te fi fi SH="${TEST_COMMAND%% *}" - # dash tests are not stable, see #931 - if test x$FAST$SH = x1dash ; then - continue - fi - if test x$FAST$SH = x1fish ; then - continue - fi if test "x$ONLY_SHELL" != "x" && test "x$ONLY_SHELL" != "x$SH" ; then continue fi @@ -378,7 +375,13 @@ if test -z "${ONLY_SHELL}" || test "x${ONLY_SHELL%sh}" != "x${ONLY_SHELL}" || te fi echo ">>> $(readlink "tests/shell/path/$SH")" if ! run_test $TEST_TYPE $TEST_CLIENT $TEST_COMMAND ; then - fail "$SH-$TEST_TYPE-$TEST_CLIENT:test" F "Failed checking $TEST_COMMAND" + ALLOW_FAILURE_ARG= + # dash tests are not stable, see #931 + # also do not allow fish tests to spoil the build + if test x$FAST$SH = x1dash || test x$FAST$SH = x1fish ; then + ALLOW_FAILURE_ARG="--allow-failure" + fi + fail $ALLOW_FAILURE_ARG "$SH-$TEST_TYPE-$TEST_CLIENT:test" F "Failed checking $TEST_COMMAND" fi done done @@ -448,7 +451,8 @@ if test "x${ONLY_SHELL}" = "x" || test "x${ONLY_SHELL}" = "xipython" ; then export POWERLINE_THEME_OVERRIDES='in.segments.left=[]' echo "> ipython" if ! run_test ipython ipython ${IPYTHON_PYTHON} -mIPython ; then - fail "ipython:test" F "Failed checking ${IPYTHON_PYTHON} -mIPython" + # Do not allow ipython tests to spoil the build + fail --allow-failure "ipython:test" F "Failed checking ${IPYTHON_PYTHON} -mIPython" fi unset POWERLINE_THEME_OVERRIDES unset POWERLINE_CONFIG_OVERRIDES