Release 2.5

- Added IPython-5 support: in new major IPython version prompt code was largerly
  reworked, resulting in different set of hacks needed for powerline to work.
  IPython still does not have features needed to avoid using hacks.
- Added stash segment.
- Fixed trailing whitespace segment: it could incorrectly report trailing
  whitespace if line ended with `b` or `'` on Python-3.
- Fixed weather segment and altered geoip service used.
- Fixed escaping used for status-left option on tmux-2.1 and higher.
This commit is contained in:
Foo 2016-07-14 00:43:25 +03:00
commit 6e2e0b2f92
34 changed files with 654 additions and 200 deletions

View File

@ -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 Lings `vim-airline
<https://github.com/bling/vim-airline>`_ project.
<https://github.com/vim-airline/vim-airline>`_ 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

View File

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

View File

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

View File

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

View File

@ -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'`\")"

View File

@ -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": [] },

View File

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

View File

@ -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"] },

View File

@ -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": [] },

View File

@ -15,6 +15,9 @@
"branch": {
"before": "BR "
},
"stash": {
"before": "ST "
},
"cwd": {
"args": {
"ellipsis": "..."

View File

@ -14,6 +14,9 @@
"branch": {
"before": " "
},
"stash": {
"before": "⌆ "
},
"cwd": {
"args": {
"ellipsis": "⋯"

View File

@ -14,6 +14,9 @@
"branch": {
"before": "🔀 "
},
"stash": {
"before": "📝"
},
"cwd": {
"args": {
"ellipsis": "⋯"

View File

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

View File

@ -14,6 +14,9 @@
"branch": {
"before": "⎇ "
},
"stash": {
"before": "⌆"
},
"cwd": {
"args": {
"ellipsis": "⋯"

View File

@ -14,6 +14,9 @@
"branch": {
"before": "BR "
},
"stash": {
"before": "ST "
},
"cwd": {
"args": {
"ellipsis": "…"

View File

@ -14,6 +14,9 @@
"branch": {
"before": "B "
},
"stash": {
"before": "S "
},
"cwd": {
"args": {
"use_path_separator": true,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <dev-segments-info>`. 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.

View File

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

View File

@ -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 youre in a VPN or if your
IP address is registered at another location.

View File

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

View File

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

View File

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

View File

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

View File

@ -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":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","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":"<![CDATA[<img src=\"http://l.yimg.com/a/i/us/we/52/23.gif\"/>\n<BR />\n<b>Current Conditions:</b>\n<BR />Breezy\n<BR />\n<BR />\n<b>Forecast:</b>\n<BR /> Fri - Partly Cloudy. High: 71Low: 48\n<BR /> Sat - Mostly Cloudy. High: 54Low: 44\n<BR /> Sun - Showers. High: 55Low: 43\n<BR /> Mon - Mostly Cloudy. High: 54Low: 42\n<BR /> Tue - Mostly Cloudy. High: 57Low: 43\n<BR />\n<BR />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/\">Full Forecast at Yahoo! Weather</a>\n<BR />\n<BR />\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)\n<BR />\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":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","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":"<![CDATA[<img src=\"http://l.yimg.com/a/i/us/we/52/33.gif\"/>\n<BR />\n<b>Current Conditions:</b>\n<BR />Mostly Clear\n<BR />\n<BR />\n<b>Forecast:</b>\n<BR /> Fri - Partly Cloudy. High: 62Low: 41\n<BR /> Sat - Partly Cloudy. High: 64Low: 43\n<BR /> Sun - Partly Cloudy. High: 63Low: 44\n<BR /> Mon - Rain. High: 60Low: 47\n<BR /> Tue - Rain. High: 64Low: 48\n<BR />\n<BR />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/\">Full Forecast at Yahoo! Weather</a>\n<BR />\n<BR />\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)\n<BR />\n]]>","guid":{"isPermaLink":"false"}}}}}}'
else:
raise NotImplementedError

View File

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

View File

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

View File

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

View File

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