From 87476e41e9b6e911a4813a677cb7af01d0f8a18d Mon Sep 17 00:00:00 2001 From: ReadmeCritic Date: Thu, 21 Apr 2016 09:27:16 -0700 Subject: [PATCH 01/21] Update README URLs based on HTTP redirects --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4a9f3140..41b81924 100644 --- a/README.rst +++ b/README.rst @@ -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 From c9ca8e06d1cdc4e8621f94c3593f0eb840abfb8c Mon Sep 17 00:00:00 2001 From: XZS Date: Fri, 22 Apr 2016 14:50:07 +0200 Subject: [PATCH 02/21] provide stash counter Some version control systems have an area where changes can be stored as temporary work in progress instead publishing them to the history. This segment is intended to display their count, reminding the developer about open ends in the current repository. The underlying implementation is VCS-specific and has to be provided by the respective library module. --- powerline/segments/common/vcs.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) 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``. +''') From 802791e979fb1fee6483f7c61d4c57b30ada1f0f Mon Sep 17 00:00:00 2001 From: XZS Date: Wed, 27 Apr 2016 12:30:26 +0200 Subject: [PATCH 03/21] test stash segment --- tests/test_segments.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_segments.py b/tests/test_segments.py index 0a7adcbf..207c2843 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' From 4d62732ed558f72b4bd28d591dc2155380e38b03 Mon Sep 17 00:00:00 2001 From: XZS Date: Fri, 22 Apr 2016 14:56:54 +0200 Subject: [PATCH 04/21] implement stash backend for git --- powerline/lib/vcs/git.py | 10 ++++++++++ 1 file changed, 10 insertions(+) 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: From a8f3d016a7f5bcfc0a9f5bff98b2361a525d4aec Mon Sep 17 00:00:00 2001 From: XZS Date: Wed, 27 Apr 2016 12:28:05 +0200 Subject: [PATCH 05/21] test git stash backend --- tests/test_lib.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) 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() From c91bcd9ba1f8f2cb0b7209aa0ba85957107dad64 Mon Sep 17 00:00:00 2001 From: XZS Date: Fri, 22 Apr 2016 14:58:06 +0200 Subject: [PATCH 06/21] colorize stash segment As the stash represents unfinished work, it uses the same colors as dirty branches and copies the respective divider colorization. --- powerline/config_files/colorschemes/default.json | 2 ++ powerline/config_files/colorschemes/solarized.json | 1 + powerline/config_files/colorschemes/vim/solarized.json | 1 + powerline/config_files/colorschemes/vim/solarizedlight.json | 2 ++ 4 files changed, 6 insertions(+) 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": [] }, From fd54f633476ef9d5acd9b2ad78f167726e424f4f Mon Sep 17 00:00:00 2001 From: XZS Date: Sat, 23 Apr 2016 10:05:26 +0200 Subject: [PATCH 07/21] indicate stash with symbols Like already in place for the branch, Unicode symbols or letter indicators make it easier to tell the stash counter apart from other segments. --- powerline/config_files/themes/ascii.json | 3 +++ powerline/config_files/themes/powerline.json | 3 +++ powerline/config_files/themes/powerline_unicode7.json | 3 +++ powerline/config_files/themes/unicode.json | 3 +++ powerline/config_files/themes/unicode_terminus.json | 3 +++ powerline/config_files/themes/unicode_terminus_condensed.json | 3 +++ 6 files changed, 18 insertions(+) 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/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, From 72053f688f6c0dd24a4964e32ef504ceeb3c7806 Mon Sep 17 00:00:00 2001 From: XZS Date: Fri, 22 Apr 2016 14:59:20 +0200 Subject: [PATCH 08/21] include stash in default shell layout As it only shows up when any stash is present, only relevant users will see it. The priority was chosen slightly lower than the branch name, as the branch name is more relevant to check upon often in between repository work while the stashes are more likely only checked between task chunks. --- powerline/config_files/themes/shell/default.json | 4 ++++ 1 file changed, 4 insertions(+) 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 From e027c5ed474649cc9e73f8455d94ce89b0a9e719 Mon Sep 17 00:00:00 2001 From: XZS Date: Fri, 22 Apr 2016 15:03:39 +0200 Subject: [PATCH 09/21] expose stash to Vim It needs the same modifications as the branch segment to correctly function. --- powerline/segments/vim/__init__.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py index 8c740c95..28bcef67 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): From f4cbeaff9bd9163210a81a7158f4202e77bcd9dd Mon Sep 17 00:00:00 2001 From: XZS Date: Wed, 27 Apr 2016 12:30:36 +0200 Subject: [PATCH 10/21] test Vim variant of stash segment --- tests/test_segments.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_segments.py b/tests/test_segments.py index 207c2843..f15fffde 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -1413,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() From 4a688a74679adb37a74196c6147f493e46be0821 Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 30 Apr 2016 16:39:51 +0300 Subject: [PATCH 11/21] Fix left segments support in tmux-2.1 --- powerline/bindings/tmux/powerline_tmux_2.1_plus.conf | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 powerline/bindings/tmux/powerline_tmux_2.1_plus.conf 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'`\")" From 41a50fe5b24b07282151f8576c43550cca8b1e64 Mon Sep 17 00:00:00 2001 From: Phil Thomas Date: Thu, 12 May 2016 10:00:40 -0500 Subject: [PATCH 12/21] Use different query to retrieve weather Also switches geoip service. Credit for fix to @gsusrafael Fixes #1571 --- powerline/segments/common/wthr.py | 17 ++++++------ tests/lib/__init__.py | 8 +++--- tests/test_segments.py | 46 +++++++++++++++---------------- 3 files changed, 36 insertions(+), 35 deletions(-) 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/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_segments.py b/tests/test_segments.py index f15fffde..05484fe1 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -867,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() @@ -1003,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)] From 436be7dacbe6c27ed287ccc744b05002007be6d5 Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Wed, 30 Mar 2016 00:09:16 +0800 Subject: [PATCH 13/21] Increase socket backlog number for `powerline-daemon` --- scripts/powerline-daemon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() From 897751838c29dd22b82511ef9b2c3ff13554efbe Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Fri, 8 Jul 2016 00:52:06 -0700 Subject: [PATCH 14/21] README: Use the svg Travis image --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 41b81924..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 From 2954c833301fbd40bf68c1edc5add951f487b31f Mon Sep 17 00:00:00 2001 From: Foo Date: Fri, 8 Jul 2016 20:24:38 +0300 Subject: [PATCH 15/21] Add support for IPython-5 Fixes #1617 --- powerline/bindings/ipython/post_0_11.py | 127 +++++++++++++------- powerline/bindings/ipython/pre_0_11.py | 2 +- powerline/ipython.py | 9 ++ powerline/renderer.py | 21 +++- powerline/renderers/ipython/__init__.py | 52 +------- powerline/renderers/ipython/pre_5.py | 56 +++++++++ powerline/renderers/ipython/since_5.py | 152 ++++++++++++++++++++++++ powerline/renderers/shell/__init__.py | 79 +++++++----- 8 files changed, 369 insertions(+), 129 deletions(-) create mode 100644 powerline/renderers/ipython/pre_5.py create mode 100644 powerline/renderers/ipython/since_5.py diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py index 3d8aea0d..7d2174ae 100644 --- a/powerline/bindings/ipython/post_0_11.py +++ b/powerline/bindings/ipython/post_0_11.py @@ -3,10 +3,20 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct from weakref import ref -from IPython.core.prompts import PromptManager +try: + from IPython.core.prompts import PromptManager + has_prompt_manager = True +except ImportError: + from IPython.terminal.interactiveshell import TerminalInteractiveShell + 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 +else: + from powerline.renderers.ipython.since_5 import PowerlinePromptStyle, PowerlinePrompts @magics_class @@ -23,37 +33,29 @@ class PowerlineMagics(Magics): raise ValueError('Expected `reload`, but got {0}'.format(line)) -class IPythonInfo(object): - def __init__(self, shell): - self._shell = shell +if has_prompt_manager: + class PowerlinePromptManager(PromptManager): + def __init__(self, powerline, shell): + self.powerline = powerline + self.powerline_segment_info = IPythonInfo(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 + 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 class ShutdownHook(object): @@ -67,33 +69,59 @@ class ShutdownHook(object): raise TryNext() +old_prompt_manager = None +old_style = None +old_prompts = None + + 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: + renderer_module = '.pre_5' + else: + renderer_module = '.since_5' + super(ConfigurableIPythonPowerline, self).init( + renderer_module=renderer_module) + def do_setup(self, ip, shutdown_hook): - prompt_manager = PowerlinePromptManager( - powerline=self, - shell=ip.prompt_manager.shell, - ) + global old_prompt_manager + global old_style + global old_prompts + + if has_prompt_manager: + 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 + else: + if ip.pt_cli is not None: + if old_style is None: + old_style = ip.pt_cli.application.style + prev_style = ip.pt_cli.application.style + while isinstance(prev_style, PowerlinePromptStyle): + prev_style = prev_style.get_style() + new_style = PowerlinePromptStyle(lambda: prev_style) + ip.pt_cli.application.style = new_style + ip.pt_cli.renderer.style = new_style + + if old_prompts is None: + old_prompts = ip.prompts + ip.prompts = PowerlinePrompts(ip.prompts, self) + magics = PowerlineMagics(ip, self) shutdown_hook.powerline = ref(self) - - ip.prompt_manager = prompt_manager ip.register_magics(magics) -old_prompt_manager = None - - def load_ipython_extension(ip): - global old_prompt_manager - old_prompt_manager = ip.prompt_manager - powerline = ConfigurableIPythonPowerline(ip) shutdown_hook = ShutdownHook() @@ -103,4 +131,11 @@ def load_ipython_extension(ip): def unload_ipython_extension(ip): - ip.prompt_manager = old_prompt_manager + if old_prompt_manager is not None: + ip.prompt_manager = old_prompt_manager + if old_style is not None: + ip.pt_cli.application.style = old_style + ip.pt_cli.renderer.style = old_style + ip.prompts = old_prompts + old_prompt_manager = None + old_style = 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/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/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..17e05453 --- /dev/null +++ b/powerline/renderers/ipython/since_5.py @@ -0,0 +1,152 @@ +# 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 IPython.terminal.prompts import Prompts + +from powerline.renderers.ipython import IPythonRenderer +from powerline.ipython import IPythonInfo + + +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(IPythonPygmentsStyle, self).__init__() + self.missing_func = missing_func + + def __missing__(self, key): + return self.missing_func(key) + + +class PowerlinePrompts(Prompts): + '''Class that returns powerline prompts + ''' + def __init__(self, old_prompts, powerline): + self.old_prompts = old_prompts + self.shell = old_prompts.shell + self.powerline = powerline + 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=IPythonInfo(self.shell),' + ' ) + [(Token.Generic.Prompt, " ")]\n' + ' return self.last_output["{0}"]' + ).format(prompt, 'in2' if prompt == 'continuation' else prompt)) + + +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 = [] + + @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. From 5b16efa6ecde4cbcc506805830bb4b71c3e576f2 Mon Sep 17 00:00:00 2001 From: Foo Date: Fri, 8 Jul 2016 21:57:25 +0300 Subject: [PATCH 16/21] Switch how IPython is configured Now user is expected to use from powerline.bindings.ipython.since_5 import PowerlinePrompts c.TerminalInteractiveShell.prompts_class = PowerlinePrompts Note: still using hacks, now different ones. Main problem is that I cannot just register a powerline pygments style, user needs to specify his own style in order to change highlighting of non-powerline tokens (i.e. of everything, but prompt). --- powerline/bindings/ipython/post_0_11.py | 114 ++++++++++-------------- powerline/bindings/ipython/since_5.py | 79 ++++++++++++++++ powerline/renderers/ipython/since_5.py | 34 ++----- 3 files changed, 133 insertions(+), 94 deletions(-) create mode 100644 powerline/bindings/ipython/since_5.py diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py index 7d2174ae..6c1efb15 100644 --- a/powerline/bindings/ipython/post_0_11.py +++ b/powerline/bindings/ipython/post_0_11.py @@ -2,12 +2,12 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) from weakref import ref +from warnings import warn try: from IPython.core.prompts import PromptManager has_prompt_manager = True except ImportError: - from IPython.terminal.interactiveshell import TerminalInteractiveShell has_prompt_manager = False from IPython.core.magic import Magics, magics_class, line_magic @@ -15,8 +15,6 @@ from powerline.ipython import IPythonPowerline, IPythonInfo if has_prompt_manager: from powerline.ipython import RewriteResult -else: - from powerline.renderers.ipython.since_5 import PowerlinePromptStyle, PowerlinePrompts @magics_class @@ -33,6 +31,22 @@ class PowerlineMagics(Magics): raise ValueError('Expected `reload`, but got {0}'.format(line)) +old_prompt_manager = None + + +class ShutdownHook(object): + def __init__(self, ip): + self.powerline = lambda: None + ip.hooks.shutdown_hook.add(self) + + def __call__(self): + from IPython.core.hooks import TryNext + powerline = self.powerline() + if powerline is not None: + powerline.shutdown() + raise TryNext() + + if has_prompt_manager: class PowerlinePromptManager(PromptManager): def __init__(self, powerline, shell): @@ -57,43 +71,22 @@ if has_prompt_manager: else: return ret + 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) -class ShutdownHook(object): - powerline = lambda: None + def do_setup(self, ip, shutdown_hook): + global old_prompt_manager - def __call__(self): - from IPython.core.hooks import TryNext - powerline = self.powerline() - if powerline is not None: - powerline.shutdown() - raise TryNext() - - -old_prompt_manager = None -old_style = None -old_prompts = None - - -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 - global old_style - global old_prompts - - if has_prompt_manager: if old_prompt_manager is None: old_prompt_manager = ip.prompt_manager prompt_manager = PowerlinePromptManager( @@ -101,41 +94,30 @@ class ConfigurableIPythonPowerline(IPythonPowerline): shell=ip.prompt_manager.shell, ) ip.prompt_manager = prompt_manager - else: - if ip.pt_cli is not None: - if old_style is None: - old_style = ip.pt_cli.application.style - prev_style = ip.pt_cli.application.style - while isinstance(prev_style, PowerlinePromptStyle): - prev_style = prev_style.get_style() - new_style = PowerlinePromptStyle(lambda: prev_style) - ip.pt_cli.application.style = new_style - ip.pt_cli.renderer.style = new_style - if old_prompts is None: - old_prompts = ip.prompts - ip.prompts = PowerlinePrompts(ip.prompts, self) - - magics = PowerlineMagics(ip, self) - shutdown_hook.powerline = ref(self) - ip.register_magics(magics) + magics = PowerlineMagics(ip, self) + shutdown_hook.powerline = ref(self) + ip.register_magics(magics) def load_ipython_extension(ip): - 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): + global old_prompt_manager if old_prompt_manager is not None: ip.prompt_manager = old_prompt_manager - if old_style is not None: - ip.pt_cli.application.style = old_style - ip.pt_cli.renderer.style = old_style - ip.prompts = old_prompts old_prompt_manager = None - old_style = None 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/renderers/ipython/since_5.py b/powerline/renderers/ipython/since_5.py index 17e05453..8a26da72 100644 --- a/powerline/renderers/ipython/since_5.py +++ b/powerline/renderers/ipython/since_5.py @@ -12,10 +12,10 @@ except ImportError: from pygments.token import Token from prompt_toolkit.styles import DynamicStyle, Attrs -from IPython.terminal.prompts import Prompts 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 @@ -30,39 +30,13 @@ class PowerlineStyleDict(defaultdict): return defaultdict.__new__(cls) def __init__(self, missing_func): - super(IPythonPygmentsStyle, self).__init__() + super(PowerlineStyleDict, self).__init__() self.missing_func = missing_func def __missing__(self, key): return self.missing_func(key) -class PowerlinePrompts(Prompts): - '''Class that returns powerline prompts - ''' - def __init__(self, old_prompts, powerline): - self.old_prompts = old_prompts - self.shell = old_prompts.shell - self.powerline = powerline - 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=IPythonInfo(self.shell),' - ' ) + [(Token.Generic.Prompt, " ")]\n' - ' return self.last_output["{0}"]' - ).format(prompt, 'in2' if prompt == 'continuation' else prompt)) - - class PowerlinePromptStyle(DynamicStyle): def get_attrs_for_token(self, token): if ( @@ -108,6 +82,10 @@ class PowerlinePromptStyle(DynamicStyle): 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, []) From d89e7909d9f74cdd2be688de69605250a111d425 Mon Sep 17 00:00:00 2001 From: Foo Date: Fri, 8 Jul 2016 22:04:54 +0300 Subject: [PATCH 17/21] Fix tests --- tests/test_provided_config_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From 66c6de34ee4d5fc3ac99a34e9d87c720c0f0bcc2 Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 9 Jul 2016 05:22:16 +0300 Subject: [PATCH 18/21] Slow down IPython tests --- tests/test_shells/test.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_shells/test.sh b/tests/test_shells/test.sh index 8abc8532..f969c929 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 From 0fc01e07bed0a6159e551ff606b5051add8c1445 Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 9 Jul 2016 16:52:54 +0300 Subject: [PATCH 19/21] Allow ipython tests to fail --- tests/common.sh | 11 +++++++++-- tests/test_shells/test.sh | 18 +++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) 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/test_shells/test.sh b/tests/test_shells/test.sh index f969c929..40ab938a 100755 --- a/tests/test_shells/test.sh +++ b/tests/test_shells/test.sh @@ -367,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 @@ -382,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 @@ -452,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 From ed69b983b22b2f1c242077161ba93d16ddc9b8f4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 Jul 2016 19:20:31 -0400 Subject: [PATCH 20/21] Fix trailing whitespace segment on Python 3. Using str() on a bytestring produces the repr on Python 3, not some implicitly decoded string. So cast a literal instead. Fixes #1613. --- powerline/segments/vim/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py index 28bcef67..122c9e9a 100644 --- a/powerline/segments/vim/__init__.py +++ b/powerline/segments/vim/__init__.py @@ -578,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] From 29cdd7f3dc45bd6d4464f81dc889a20a78cb1e75 Mon Sep 17 00:00:00 2001 From: Foo Date: Thu, 14 Jul 2016 00:43:25 +0300 Subject: [PATCH 21/21] Update base version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2c9d758d..03a16a4b 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())