diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 4651e5ac..c2427383 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -379,10 +379,17 @@ Themes Segments are removed according to their priority, with low priority segments being removed first. - ``draw_divider`` + ``draw_hard_divider``, ``draw_soft_divider`` Whether to draw a divider between this and the adjacent segment. The - adjacent segment is to the *right* for segments on the *left* side, - and vice versa. + adjacent segment is to the *right* for segments on the *left* side, and + vice versa. Hard dividers are used between segments with different + background colors, soft ones are used between segments with same + background. Both options default to ``True``. + + ``draw_inner_divider`` + Determines whether inner soft dividers are to be drawn for function + segments. Only applicable for functions returning multiple segments. + Defaults to ``False``. ``exclude_modes`` A list of modes where this segment will be excluded: The segment is diff --git a/powerline/__init__.py b/powerline/__init__.py index 4935747f..f564bec6 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -159,8 +159,9 @@ class Powerline(object): self.config_paths = self.get_config_paths() - self.renderer_lock = Lock() self.configs_lock = Lock() + self.cr_kwargs_lock = Lock() + self.create_renderer_kwargs = {} self.shutdown_event = Event() self.configs = defaultdict(set) self.missing = defaultdict(set) @@ -276,15 +277,14 @@ class Powerline(object): # Renderer updates configuration file via segments’ .startup thus it # should be locked to prevent state when configuration was updated, # but .render still uses old renderer. - with self.renderer_lock: - try: - renderer = Renderer(**self.renderer_options) - except Exception as e: - self.pl.exception('Failed to construct renderer object: {0}', str(e)) - if not hasattr(self, 'renderer'): - raise - else: - self.renderer = renderer + try: + renderer = Renderer(**self.renderer_options) + except Exception as e: + self.pl.exception('Failed to construct renderer object: {0}', str(e)) + if not hasattr(self, 'renderer'): + raise + else: + self.renderer = renderer if not self.run_once and not self.is_alive() and self.interval is not None: self.start() @@ -397,8 +397,15 @@ class Powerline(object): '''Lock renderer from modifications and pass all arguments further to ``self.renderer.render()``. ''' - with self.renderer_lock: - return self.renderer.render(*args, **kwargs) + with self.cr_kwargs_lock: + if self.create_renderer_kwargs: + try: + self.create_renderer(**self.create_renderer_kwargs) + except Exception as e: + self.pl.exception('Failed to create renderer: {0}', str(e)) + finally: + self.create_renderer_kwargs.clear() + return self.renderer.render(*args, **kwargs) def shutdown(self): '''Lock renderer from modifications and run its ``.shutdown()`` method. @@ -406,8 +413,7 @@ class Powerline(object): self.shutdown_event.set() if self.use_daemon_threads and self.is_alive(): self.thread.join() - with self.renderer_lock: - self.renderer.shutdown() + self.renderer.shutdown() self.watcher.unsubscribe() def is_alive(self): @@ -422,6 +428,7 @@ class Powerline(object): def run(self): while not self.shutdown_event.is_set(): kwargs = {} + removes = [] with self.configs_lock: for type, paths in self.configs.items(): for path in paths: @@ -435,11 +442,12 @@ class Powerline(object): pass else: kwargs['load_' + type] = True + removes.append((type, cfg_path)) + for type, cfg_path in removes: + self.missing[type].remove(cfg_path) if kwargs: - try: - self.create_renderer(**kwargs) - except Exception as e: - self.pl.exception('Failed to create renderer: {0}', str(e)) + with self.cr_kwargs_lock: + self.create_renderer_kwargs.update(kwargs) self.shutdown_event.wait(self.interval) def __enter__(self): diff --git a/powerline/config_files/themes/ipython/in.json b/powerline/config_files/themes/ipython/in.json index bf6be670..ac979c5f 100644 --- a/powerline/config_files/themes/ipython/in.json +++ b/powerline/config_files/themes/ipython/in.json @@ -8,13 +8,13 @@ { "type": "string", "contents": "In[", - "draw_divider": false, + "draw_soft_divider": false, "highlight_group": ["prompt"] }, { "name": "prompt_count", "module": "powerline.segments.ipython", - "draw_divider": false + "draw_soft_divider": false }, { "type": "string", diff --git a/powerline/config_files/themes/ipython/out.json b/powerline/config_files/themes/ipython/out.json index 6d4eac53..11a63234 100644 --- a/powerline/config_files/themes/ipython/out.json +++ b/powerline/config_files/themes/ipython/out.json @@ -5,7 +5,7 @@ { "type": "string", "contents": "Out[", - "draw_divider": false, + "draw_soft_divider": false, "width": "auto", "align": "r", "highlight_group": ["prompt"] @@ -13,7 +13,7 @@ { "name": "prompt_count", "module": "powerline.segments.ipython", - "draw_divider": false + "draw_soft_divider": false }, { "type": "string", diff --git a/powerline/config_files/themes/ipython/rewrite.json b/powerline/config_files/themes/ipython/rewrite.json index 4616769a..47d8de0d 100644 --- a/powerline/config_files/themes/ipython/rewrite.json +++ b/powerline/config_files/themes/ipython/rewrite.json @@ -4,14 +4,14 @@ { "type": "string", "contents": "", - "draw_divider": false, + "draw_soft_divider": false, "width": "auto", "highlight_group": ["prompt"] }, { "name": "prompt_count", "module": "powerline.segments.ipython", - "draw_divider": false + "draw_soft_divider": false }, { "type": "string", diff --git a/powerline/config_files/themes/vim/cmdwin.json b/powerline/config_files/themes/vim/cmdwin.json index cffd9e1c..c300d948 100644 --- a/powerline/config_files/themes/vim/cmdwin.json +++ b/powerline/config_files/themes/vim/cmdwin.json @@ -9,7 +9,8 @@ { "type": "string", "highlight_group": ["background"], - "draw_divider": false, + "draw_soft_divider": false, + "draw_hard_divider": false, "width": "auto" } ] diff --git a/powerline/config_files/themes/vim/default.json b/powerline/config_files/themes/vim/default.json index 3eeae293..40268a3f 100644 --- a/powerline/config_files/themes/vim/default.json +++ b/powerline/config_files/themes/vim/default.json @@ -32,22 +32,22 @@ }, { "name": "readonly_indicator", - "draw_divider": false, + "draw_soft_divider": false, "after": " " }, { "name": "file_directory", "priority": 40, - "draw_divider": false + "draw_soft_divider": false }, { "name": "file_name", - "draw_divider": false + "draw_soft_divider": false }, { "name": "file_vcs_status", "before": " ", - "draw_divider": false + "draw_soft_divider": false }, { "name": "modified_indicator", @@ -56,14 +56,15 @@ { "type": "string", "highlight_group": ["background"], - "draw_divider": false, + "draw_soft_divider": false, + "draw_hard_divider": false, "width": "auto" } ], "right": [ { "name": "file_format", - "draw_divider": false, + "draw_soft_divider": false, "exclude_modes": ["nc"], "priority": 50 }, @@ -90,13 +91,13 @@ }, { "name": "line_current", - "draw_divider": false, + "draw_soft_divider": false, "width": 3, "align": "r" }, { "name": "virtcol_current", - "draw_divider": false, + "draw_soft_divider": false, "priority": 30, "before": ":", "width": 3, diff --git a/powerline/config_files/themes/vim/help.json b/powerline/config_files/themes/vim/help.json index 9fb5320c..74071057 100644 --- a/powerline/config_files/themes/vim/help.json +++ b/powerline/config_files/themes/vim/help.json @@ -3,12 +3,13 @@ "left": [ { "name": "file_name", - "draw_divider": false + "draw_soft_divider": false }, { "type": "string", "highlight_group": ["background"], - "draw_divider": false, + "draw_soft_divider": false, + "draw_hard_divider": false, "width": "auto" } ], @@ -26,7 +27,7 @@ }, { "name": "line_current", - "draw_divider": false, + "draw_soft_divider": false, "width": 3, "align": "r" } diff --git a/powerline/lib/threaded.py b/powerline/lib/threaded.py index e7f10085..5c0cfb3c 100644 --- a/powerline/lib/threaded.py +++ b/powerline/lib/threaded.py @@ -144,7 +144,7 @@ class KwThreadedSegment(ThreadedSegment): except KeyError: # Allow only to forbid to compute missing values: in either user # configuration or in subclasses. - update_state = self.compute_state(key) if update_first and self.update_first or self.run_once else None + update_state = self.compute_state(key) if ((update_first and self.update_first) or self.run_once) else None with self.write_lock: self.new_queries[key] = (monotonic(), update_state) @@ -173,12 +173,9 @@ class KwThreadedSegment(ThreadedSegment): return update_value - def set_state(self, interval=None, update_first=True, **kwargs): + def set_state(self, interval=None, **kwargs): self.set_interval(interval) - if self.update_first: - self.update_first = update_first - @staticmethod def render_one(update_state, **kwargs): return update_state diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 31d70798..6b2636af 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -505,9 +505,9 @@ vim_colorscheme_spec = (Spec( ).context_message('Error while loading vim colorscheme')) -generic_keys = set(('exclude_modes', 'include_modes', 'width', 'align', 'name', 'draw_divider', 'priority', 'after', 'before')) +generic_keys = set(('exclude_modes', 'include_modes', 'width', 'align', 'name', 'draw_soft_divider', 'draw_hard_divider', 'priority', 'after', 'before')) type_keys = { - 'function': set(('args', 'module')), + 'function': set(('args', 'module', 'draw_inner_divider')), 'string': set(('contents', 'type', 'highlight_group', 'divider_highlight_group')), 'filler': set(('type', 'highlight_group', 'divider_highlight_group')), } @@ -797,7 +797,9 @@ segments_spec = Spec().optional().list( name=Spec().re('^[a-zA-Z_]\w+$').func(check_segment_name).optional(), exclude_modes=Spec().list(vim_mode_spec()).optional(), include_modes=Spec().list(vim_mode_spec()).optional(), - draw_divider=Spec().type(bool).optional(), + draw_hard_divider=Spec().type(bool).optional(), + draw_soft_divider=Spec().type(bool).optional(), + draw_inner_divider=Spec().type(bool).optional(), module=segment_module_spec(), priority=Spec().cmp('ge', -1).optional(), after=Spec().type(unicode).optional(), diff --git a/powerline/renderer.py b/powerline/renderer.py index 6a2a62c4..324aa982 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -144,9 +144,10 @@ class Renderer(object): divider_highlighted = '' contents_raw = segment['contents'] contents_highlighted = '' + draw_divider = segment['draw_' + divider_type + '_divider'] # Pad segments first - if segment['draw_divider'] or (divider_type == 'hard' and segment['width'] != 'auto'): + if draw_divider: if segment['side'] == 'left': contents_raw = outer_padding + (segment['_space_left'] * ' ') + contents_raw + ((divider_spaces + segment['_space_right']) * ' ') else: @@ -174,7 +175,7 @@ class Renderer(object): contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) # Append padded raw and highlighted segments to the rendered segment variables - if segment['draw_divider'] or (divider_type == 'hard' and segment['width'] != 'auto'): + if draw_divider: if segment['side'] == 'left': segment['_rendered_raw'] += contents_raw + divider_raw segment['_rendered_hl'] += contents_highlighted + divider_highlighted diff --git a/powerline/segment.py b/powerline/segment.py index 8f166013..8a2483ff 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -74,7 +74,9 @@ def gen_segment_getter(ext, path, theme_configs, default_module=None): 'contents': contents, 'args': get_key(segment, module, 'args', {}) if segment_type == 'function' else {}, 'priority': segment.get('priority', -1), - 'draw_divider': segment.get('draw_divider', True), + 'draw_hard_divider': segment.get('draw_hard_divider', True), + 'draw_soft_divider': segment.get('draw_soft_divider', True), + 'draw_inner_divider': segment.get('draw_inner_divider', False), 'side': side, 'exclude_modes': segment.get('exclude_modes', []), 'include_modes': segment.get('include_modes', []), diff --git a/powerline/segments/common.py b/powerline/segments/common.py index 94bf3b43..558d807f 100644 --- a/powerline/segments/common.py +++ b/powerline/segments/common.py @@ -117,7 +117,7 @@ Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``. @requires_segment_info -def cwd(pl, segment_info, dir_shorten_len=None, dir_limit_depth=None): +def cwd(pl, segment_info, dir_shorten_len=None, dir_limit_depth=None, use_path_separator=False): '''Return the current working directory. Returns a segment list to create a breadcrumb-like effect. @@ -126,7 +126,8 @@ def cwd(pl, segment_info, dir_shorten_len=None, dir_limit_depth=None): shorten parent directory names to this length (e.g. :file:`/long/path/to/powerline` → :file:`/l/p/t/powerline`) :param int dir_limit_depth: limit directory depth to this number (e.g. :file:`/long/path/to/powerline` → :file:`⋯/to/powerline`) - + :param bool use_path_separator: + Use path separator in place of soft divider. Divider highlight group used: ``cwd:divider``. @@ -155,14 +156,20 @@ def cwd(pl, segment_info, dir_shorten_len=None, dir_limit_depth=None): ret = [] if not cwd[0]: cwd[0] = '/' + draw_inner_divider = not use_path_separator for part in cwd: if not part: continue + if use_path_separator: + part += os.sep ret.append({ 'contents': part, 'divider_highlight_group': 'cwd:divider', + 'draw_inner_divider': draw_inner_divider, }) ret[-1]['highlight_group'] = ['cwd:current_folder', 'cwd'] + if use_path_separator: + ret[-1]['contents'] = ret[-1]['contents'][:-1] return ret @@ -438,7 +445,6 @@ class WeatherSegment(ThreadedSegment): { 'contents': temp_format.format(temp=converted_temp), 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], - 'draw_divider': False, 'divider_highlight_group': 'background:divider', 'gradient_level': gradient_level, }, @@ -521,11 +527,9 @@ def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2): ret.append({ 'contents': format.format(avg=avg), 'highlight_group': ['system_load_gradient', 'system_load'], - 'draw_divider': False, 'divider_highlight_group': 'background:divider', 'gradient_level': gradient_level, }) - ret[0]['draw_divider'] = True ret[0]['contents'] += ' ' ret[1]['contents'] += ' ' return ret @@ -612,7 +616,7 @@ def user(pl, segment_info=None): 'contents': username, 'highlight_group': 'user' if euid != 0 else ['superuser', 'user'], }] -if 'psutil' in globals(): +if 'psutil' not in globals(): user = requires_segment_info(user) diff --git a/powerline/segments/shell.py b/powerline/segments/shell.py index 66699ac3..e870048a 100644 --- a/powerline/segments/shell.py +++ b/powerline/segments/shell.py @@ -22,7 +22,7 @@ def last_pipe_status(pl, segment_info): ''' last_pipe_status = segment_info['args'].last_pipe_status if any(last_pipe_status): - return [{"contents": str(status), "highlight_group": "exit_fail" if status else "exit_success"} + return [{'contents': str(status), 'highlight_group': 'exit_fail' if status else 'exit_success', 'draw_inner_divider': True} for status in last_pipe_status] else: return None diff --git a/powerline/theme.py b/powerline/theme.py index ed5e5788..0b7cdd7a 100644 --- a/powerline/theme.py +++ b/powerline/theme.py @@ -1,7 +1,5 @@ # vim:fileencoding=utf-8:noet -from copy import copy - from .segment import gen_segment_getter @@ -91,19 +89,35 @@ class Theme(object): if contents is None: continue if isinstance(contents, list): - segment_base = copy(segment) + segment_base = segment.copy() if contents: - for key in ('before', 'after'): + draw_divider_position = -1 if side == 'left' else 0 + for key, i, newval in ( + ('before', 0, ''), + ('after', -1, ''), + ('draw_soft_divider', draw_divider_position, True), + ('draw_hard_divider', draw_divider_position, True), + ): try: - contents[0][key] = segment_base.pop(key) - segment_base[key] = '' + contents[i][key] = segment_base.pop(key) + segment_base[key] = newval except KeyError: pass - for subsegment in contents: - segment_copy = copy(segment_base) + draw_inner_divider = None + if side == 'right': + append = parsed_segments.append + else: + pslen = len(parsed_segments) + append = lambda item: parsed_segments.insert(pslen, item) + + for subsegment in (contents if side == 'right' else reversed(contents)): + segment_copy = segment_base.copy() segment_copy.update(subsegment) - parsed_segments.append(segment_copy) + if draw_inner_divider is not None: + segment_copy['draw_soft_divider'] = draw_inner_divider + draw_inner_divider = segment_copy.pop('draw_inner_divider', None) + append(segment_copy) else: segment['contents'] = contents parsed_segments.append(segment) @@ -124,4 +138,4 @@ class Theme(object): # We need to yield a copy of the segment, or else mode-dependent # segment contents can't be cached correctly e.g. when caching # non-current window contents for vim statuslines - yield copy(segment) + yield segment.copy() diff --git a/tests/lib/config_mock.py b/tests/lib/config_mock.py new file mode 100644 index 00000000..83a27f75 --- /dev/null +++ b/tests/lib/config_mock.py @@ -0,0 +1,119 @@ +# vim:fileencoding=utf-8:noet +from threading import Lock +from powerline.renderer import Renderer +from powerline import Powerline +from copy import deepcopy + + +access_log = [] +access_lock = Lock() + + +def load_json_config(config, config_file_path, *args, **kwargs): + global access_log + with access_lock: + access_log.append(config_file_path) + try: + return deepcopy(config[config_file_path]) + except KeyError: + raise IOError(config_file_path) + + +def find_config_file(config, search_paths, config_file): + if config_file.endswith('raise') and config_file not in config: + raise IOError('fcf:' + config_file) + return config_file + + +def pop_events(): + global access_log + with access_lock: + r = access_log[:] + access_log = [] + return r + + +class Watcher(object): + events = set() + lock = Lock() + + def watch(self, file): + pass + + def __call__(self, file): + if file in self.events: + with self.lock: + self.events.remove(file) + return True + return False + + def _reset(self, files): + with self.lock: + self.events.clear() + self.events.update(files) + + def unsubscribe(self): + pass + + +class Logger(object): + def __init__(self): + self.messages = [] + self.lock = Lock() + + def _add_msg(self, attr, msg): + with self.lock: + self.messages.append(attr + ':' + msg) + + def _pop_msgs(self): + with self.lock: + r = self.messages + self.messages = [] + return r + + def __getattr__(self, attr): + return lambda *args, **kwargs: self._add_msg(attr, *args, **kwargs) + + +class SimpleRenderer(Renderer): + def hlstyle(self, fg=None, bg=None, attr=None): + return '<{fg} {bg} {attr}>'.format(fg=fg and fg[0], bg=bg and bg[0], attr=attr) + + +class TestPowerline(Powerline): + _created = False + + @staticmethod + def get_local_themes(local_themes): + return local_themes + + def _will_create_renderer(self): + return self.create_renderer_kwargs + + +renderer = SimpleRenderer + + +def get_powerline(**kwargs): + return TestPowerline( + ext='test', + renderer_module='tests.lib.config_mock', + interval=0, + logger=Logger(), + watcher=Watcher(), + **kwargs + ) + + +def swap_attributes(config_container, powerline_module, replaces): + if not replaces: + replaces = { + 'watcher': Watcher(), + 'load_json_config': lambda *args: load_json_config(config_container['config'], *args), + 'find_config_file': lambda *args: find_config_file(config_container['config'], *args), + } + for attr, val in replaces.items(): + old_val = getattr(powerline_module, attr) + setattr(powerline_module, attr, val) + replaces[attr] = old_val + return replaces diff --git a/tests/test_config_reload.py b/tests/test_config_reload.py index d67eeeaf..d74f2cbe 100644 --- a/tests/test_config_reload.py +++ b/tests/test_config_reload.py @@ -2,55 +2,13 @@ from __future__ import unicode_literals import powerline as powerline_module import time -from powerline.renderer import Renderer from tests import TestCase from tests.lib import replace_item +from tests.lib.config_mock import swap_attributes, get_powerline, pop_events from copy import deepcopy from threading import Lock -class Watcher(object): - events = set() - lock = Lock() - - def watch(self, file): - pass - - def __call__(self, file): - if file in self.events: - with self.lock: - self.events.remove(file) - return True - return False - - def _reset(self, files): - with self.lock: - self.events.clear() - self.events.update(files) - - def unsubscribe(self): - pass - - -class Logger(object): - def __init__(self): - self.messages = [] - self.lock = Lock() - - def _add_msg(self, msg): - with self.lock: - self.messages.append(msg) - - def _pop_msgs(self): - with self.lock: - r = self.messages - self.messages = [] - return r - - def __getattr__(self, attr): - return self._add_msg - - config = { 'config': { 'common': { @@ -134,74 +92,13 @@ config = { } -access_log = [] -access_lock = Lock() - - -def load_json_config(config_file_path, *args, **kwargs): - global access_log - with access_lock: - access_log.append(config_file_path) - try: - return deepcopy(config[config_file_path]) - except KeyError: - raise IOError(config_file_path) - - -def find_config_file(search_paths, config_file): - if config_file.endswith('raise') and config_file not in config: - raise IOError('fcf:' + config_file) - return config_file - - -class SimpleRenderer(Renderer): - def hlstyle(self, fg=None, bg=None, attr=None): - return '<{fg} {bg} {attr}>'.format(fg=fg and fg[0], bg=bg and bg[0], attr=attr) - - -class TestPowerline(powerline_module.Powerline): - _created = False - - @staticmethod - def get_local_themes(local_themes): - return local_themes - - def create_renderer(self, *args, **kwargs): - try: - r = super(TestPowerline, self).create_renderer(*args, **kwargs) - finally: - self._created = True - return r - - def _created_renderer(self): - if self._created: - self._created = False - return True - return False - - -renderer = SimpleRenderer - - -def get_powerline(**kwargs): - return TestPowerline( - ext='test', - renderer_module='tests.test_config_reload', - interval=0, - logger=Logger(), - watcher=Watcher(), - **kwargs - ) - - def sleep(interval): time.sleep(interval) def add_watcher_events(p, *args, **kwargs): - p._created_renderer() p.watcher._reset(args) - while not p._created_renderer(): + while not p._will_create_renderer(): sleep(kwargs.get('interval', 0.000001)) if not kwargs.get('wait', True): return @@ -209,26 +106,23 @@ def add_watcher_events(p, *args, **kwargs): class TestConfigReload(TestCase): def assertAccessEvents(self, *args): - global access_log - with access_lock: - self.assertEqual(set(access_log), set(args)) - access_log = [] + self.assertEqual(set(pop_events()), set(args)) def test_noreload(self): with get_powerline(run_once=True) as p: with replace_item(globals(), 'config', deepcopy(config)): - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') config['config']['common']['spaces'] = 1 add_watcher_events(p, 'config', wait=False, interval=0.05) # When running once thread should not start - self.assertAccessEvents() self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertEqual(p.logger._pop_msgs(), []) - # Without the following assertion test_reload_colors may fail - # for unknown reason (with AssertionError telling about “config” - # accessed one more time then needed) self.assertAccessEvents() + self.assertEqual(p.logger._pop_msgs(), []) + # Without the following assertion test_reload_colors may fail for + # unknown reason (with AssertionError telling about “config” accessed + # one more time then needed) + pop_events() def test_reload_main(self): with get_powerline(run_once=False) as p: @@ -238,61 +132,62 @@ class TestConfigReload(TestCase): config['config']['common']['spaces'] = 1 add_watcher_events(p, 'config') - self.assertAccessEvents('config') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') + self.assertAccessEvents('config') self.assertEqual(p.logger._pop_msgs(), []) config['config']['ext']['test']['theme'] = 'nonexistent' add_watcher_events(p, 'config') - self.assertAccessEvents('config', 'themes/test/nonexistent') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') + self.assertAccessEvents('config', 'themes/test/nonexistent') # It should normally handle file missing error - self.assertEqual(p.logger._pop_msgs(), ['test:Failed to create renderer: themes/test/nonexistent']) + self.assertEqual(p.logger._pop_msgs(), ['exception:test:Failed to create renderer: themes/test/nonexistent']) config['config']['ext']['test']['theme'] = 'default' add_watcher_events(p, 'config') - self.assertAccessEvents('config', 'themes/test/default') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') + self.assertAccessEvents('config', 'themes/test/default') self.assertEqual(p.logger._pop_msgs(), []) config['config']['ext']['test']['colorscheme'] = 'nonexistent' add_watcher_events(p, 'config') - self.assertAccessEvents('config', 'colorschemes/test/nonexistent') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') + self.assertAccessEvents('config', 'colorschemes/test/nonexistent') # It should normally handle file missing error - self.assertEqual(p.logger._pop_msgs(), ['test:Failed to create renderer: colorschemes/test/nonexistent']) + self.assertEqual(p.logger._pop_msgs(), ['exception:test:Failed to create renderer: colorschemes/test/nonexistent']) config['config']['ext']['test']['colorscheme'] = '2' add_watcher_events(p, 'config') - self.assertAccessEvents('config', 'colorschemes/test/2') self.assertEqual(p.render(), '<2 3 1> s <3 4 False>>><1 4 4>g <4 False False>>>') + self.assertAccessEvents('config', 'colorschemes/test/2') self.assertEqual(p.logger._pop_msgs(), []) config['config']['ext']['test']['theme'] = '2' add_watcher_events(p, 'config') - self.assertAccessEvents('config', 'themes/test/2') self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>>') + self.assertAccessEvents('config', 'themes/test/2') self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.renderer.local_themes, None) config['config']['ext']['test']['local_themes'] = 'something' add_watcher_events(p, 'config') - self.assertAccessEvents('config') self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>>') + self.assertAccessEvents('config') self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.renderer.local_themes, 'something') + pop_events() def test_reload_unexistent(self): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') config['config']['ext']['test']['colorscheme'] = 'nonexistentraise' add_watcher_events(p, 'config') - self.assertAccessEvents('config') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertEqual(p.logger._pop_msgs(), ['test:Failed to create renderer: fcf:colorschemes/test/nonexistentraise']) + self.assertAccessEvents('config') + self.assertEqual(p.logger._pop_msgs(), ['exception:test:Failed to create renderer: fcf:colorschemes/test/nonexistentraise']) config['colorschemes/test/nonexistentraise'] = { 'groups': { @@ -300,65 +195,62 @@ class TestConfigReload(TestCase): "str2": {"fg": "col2", "bg": "col4", "attr": ["underline"]}, }, } - while not p._created_renderer(): + while not p._will_create_renderer(): sleep(0.000001) - self.assertAccessEvents('colorschemes/test/nonexistentraise') self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><2 4 4>g<4 False False>>>') + self.assertAccessEvents('colorschemes/test/nonexistentraise') self.assertEqual(p.logger._pop_msgs(), []) + pop_events() def test_reload_colors(self): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') config['colors']['colors']['col1'] = 5 add_watcher_events(p, 'colors') - self.assertAccessEvents('colors') self.assertEqual(p.render(), '<5 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('colors') self.assertEqual(p.logger._pop_msgs(), []) + pop_events() def test_reload_colorscheme(self): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') config['colorschemes/test/default']['groups']['str1']['bg'] = 'col3' add_watcher_events(p, 'colorschemes/test/default') - self.assertAccessEvents('colorschemes/test/default') self.assertEqual(p.render(), '<1 3 1> s<3 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('colorschemes/test/default') self.assertEqual(p.logger._pop_msgs(), []) + pop_events() def test_reload_theme(self): with get_powerline(run_once=False) as p: with replace_item(globals(), 'config', deepcopy(config)): - self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('config', 'colors', 'colorschemes/test/default', 'themes/test/default') config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' add_watcher_events(p, 'themes/test/default') - self.assertAccessEvents('themes/test/default') self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents('themes/test/default') self.assertEqual(p.logger._pop_msgs(), []) + pop_events() -replaces = { - 'watcher': Watcher(), - 'load_json_config': load_json_config, - 'find_config_file': find_config_file, -} +replaces = {} -def swap_attributes(): +def setUpModule(): global replaces - for attr, val in replaces.items(): - old_val = getattr(powerline_module, attr) - setattr(powerline_module, attr, val) - replaces[attr] = old_val + replaces = swap_attributes(globals(), powerline_module, replaces) -tearDownModule = setUpModule = swap_attributes +tearDownModule = setUpModule if __name__ == '__main__': diff --git a/tests/test_segments.py b/tests/test_segments.py index 40cc7cd0..d8c7bb3d 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -29,10 +29,11 @@ class TestShell(TestCase): segment_info['args'].last_pipe_status = [0, 0, 0] self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), None) segment_info['args'].last_pipe_status = [0, 2, 0] - self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), - [{'contents': '0', 'highlight_group': 'exit_success'}, - {'contents': '2', 'highlight_group': 'exit_fail'}, - {'contents': '0', 'highlight_group': 'exit_success'}]) + self.assertEqual(shell.last_pipe_status(pl=pl, segment_info=segment_info), [ + {'contents': '0', 'highlight_group': 'exit_success', 'draw_inner_divider': True}, + {'contents': '2', 'highlight_group': 'exit_fail', 'draw_inner_divider': True}, + {'contents': '0', 'highlight_group': 'exit_success', 'draw_inner_divider': True} + ]) class TestCommon(TestCase): @@ -102,34 +103,48 @@ class TestCommon(TestCase): segment_info = {'getcwd': getcwd, 'home': None} with replace_attr(common, 'os', new_os): cwd[0] = '/abc/def/ghi/foo/bar' - self.assertEqual(common.cwd(pl=pl, segment_info=segment_info), - [{'contents': '/', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'abc', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'def', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'foo', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'highlight_group': ['cwd:current_folder', 'cwd']}]) + self.assertEqual(common.cwd(pl=pl, segment_info=segment_info), [ + {'contents': '/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'abc', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'def', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'ghi', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_group': ['cwd:current_folder', 'cwd']}, + ]) segment_info['home'] = '/abc/def/ghi' - self.assertEqual(common.cwd(pl=pl, segment_info=segment_info), - [{'contents': '~', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'foo', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'highlight_group': ['cwd:current_folder', 'cwd']}]) - self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3), - [{'contents': '~', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'foo', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'highlight_group': ['cwd:current_folder', 'cwd']}]) - self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1), - [{'contents': '⋯', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'highlight_group': ['cwd:current_folder', 'cwd']}]) - self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), - [{'contents': '~', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'fo', 'divider_highlight_group': 'cwd:divider'}, - {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'highlight_group': ['cwd:current_folder', 'cwd']}]) + self.assertEqual(common.cwd(pl=pl, segment_info=segment_info), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_group': ['cwd:current_folder', 'cwd']}, + ]) + self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=3), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'foo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_group': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1), [ + {'contents': '⋯', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_group': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=1, use_path_separator=True), [ + {'contents': '⋯/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_group': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), [ + {'contents': '~', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'fo', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': True, 'highlight_group': ['cwd:current_folder', 'cwd']} + ]) + self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2, use_path_separator=True), [ + {'contents': '~/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'fo/', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False}, + {'contents': 'bar', 'divider_highlight_group': 'cwd:divider', 'draw_inner_divider': False, 'highlight_group': ['cwd:current_folder', 'cwd']} + ]) ose = OSError() ose.errno = 2 cwd[0] = ose self.assertEqual(common.cwd(pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2), - [{'contents': '[not found]', 'divider_highlight_group': 'cwd:divider', 'highlight_group': ['cwd:current_folder', 'cwd']}]) + [{'contents': '[not found]', 'divider_highlight_group': 'cwd:divider', 'highlight_group': ['cwd:current_folder', 'cwd'], 'draw_inner_divider': True}]) cwd[0] = OSError() self.assertRaises(OSError, common.cwd, pl=pl, segment_info=segment_info, dir_limit_depth=2, dir_shorten_len=2) cwd[0] = ValueError() @@ -175,35 +190,35 @@ class TestCommon(TestCase): with replace_attr(common, 'urllib_read', urllib_read): self.assertEqual(common.weather(pl=pl), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} ]) self.assertEqual(common.weather(pl=pl, temp_coldest=0, temp_hottest=100), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 0} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 0} ]) self.assertEqual(common.weather(pl=pl, temp_coldest=-100, temp_hottest=-50), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 100} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 100} ]) self.assertEqual(common.weather(pl=pl, icons={'cloudy': 'o'}), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'o '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} ]) self.assertEqual(common.weather(pl=pl, icons={'partly_cloudy_day': 'x'}), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'x '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9°C', 'gradient_level': 30.0} ]) self.assertEqual(common.weather(pl=pl, unit='F'), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '16°F', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '16°F', 'gradient_level': 30.0} ]) self.assertEqual(common.weather(pl=pl, unit='K'), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '264K', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '264K', 'gradient_level': 30.0} ]) self.assertEqual(common.weather(pl=pl, temp_format='{temp:.1e}C'), [ {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '}, - {'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9.0e+00C', 'gradient_level': 30.0} + {'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '-9.0e+00C', 'gradient_level': 30.0} ]) def test_system_load(self): @@ -211,13 +226,13 @@ class TestCommon(TestCase): with replace_module_module(common, 'os', getloadavg=lambda: (7.5, 3.5, 1.5)): with replace_attr(common, 'cpu_count', lambda: 2): self.assertEqual(common.system_load(pl=pl), - [{'contents': '7.5 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'draw_divider': True, 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, - {'contents': '3.5 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'draw_divider': False, 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}, - {'contents': '1.5', 'highlight_group': ['system_load_gradient', 'system_load'], 'draw_divider': False, 'divider_highlight_group': 'background:divider', 'gradient_level': 0}]) + [{'contents': '7.5 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + {'contents': '3.5 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}, + {'contents': '1.5', 'highlight_group': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 0}]) self.assertEqual(common.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1), - [{'contents': '8 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'draw_divider': True, 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, - {'contents': '4 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'draw_divider': False, 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, - {'contents': '2', 'highlight_group': ['system_load_gradient', 'system_load'], 'draw_divider': False, 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}]) + [{'contents': '8 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + {'contents': '4 ', 'highlight_group': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100}, + {'contents': '2', 'highlight_group': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}]) def test_cpu_load_percent(self): pl = Pl()