From 145e1c2050a7a84f265e8dc373fa9bdd781643d6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 5 Aug 2014 19:36:33 +0400 Subject: [PATCH 01/16] Only check that all colorschemes have theme, but not the opposite --- powerline/lint/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index f4e77032..6af261c6 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -1329,7 +1329,7 @@ def check(paths=None, debug=False): name = os.path.basename(path)[:-5] configs['top_' + typ][name] = path - diff = set(configs['themes']) ^ set(configs['colorschemes']) + diff = set(configs['colorschemes']) - set(configs['themes']) if diff: hadproblem = True for ext in diff: From bdde4ae99f3cbba53961d372bb2722aa90bb71cb Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 5 Aug 2014 20:29:03 +0400 Subject: [PATCH 02/16] Implement theme hierarchy Fixes #783 --- docs/source/configuration.rst | 8 +- docs/source/configuration/reference.rst | 52 +++- powerline/__init__.py | 115 +++++---- powerline/config_files/config.json | 13 +- powerline/config_files/themes/powerline.json | 13 + powerline/lint/__init__.py | 258 +++++++++++++------ powerline/theme.py | 4 +- tests/test_config_merging.py | 76 +++--- tests/test_config_reload.py | 86 +++++-- tests/test_configuration.py | 125 +++++++-- 10 files changed, 511 insertions(+), 239 deletions(-) create mode 100644 powerline/config_files/themes/powerline.json diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 8d21db77..88663a6c 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -17,9 +17,11 @@ Powerline provides default configurations in the following locations: :file:`powerline/config.json` :ref:`Colorschemes ` :file:`powerline/colorschemes/{name}.json`, - :file:`powerline/colorscheme/__main__.json`, + :file:`powerline/colorscheme/{extension}/__main__.json`, :file:`powerline/colorschemes/{extension}/{name}.json` :ref:`Themes ` + :file:`powerline/themes/{top_theme}.json`, + :file:`powerline/themes/{extension}/__main__.json`, :file:`powerline/themes/{extension}/default.json` The default configuration files are stored in the main package. User @@ -48,6 +50,10 @@ overrides `. corresponding values are both dictionaries in which case these dictionaries are merged and key is assigned the result of the merge. +.. note:: Some configuration files (i.e. themes and colorschemes) have two level + of merging: first happens merging described above, second theme- or + colorscheme-specific merging happens. + .. _quick-guide: Quick setup guide diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index 28c11863..6b899ea7 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -58,15 +58,6 @@ Common configuration is a subdictionary that is a value of ``common`` key in codes thus rendering powerline prompt colorless. Valid values: ``"tmux"``, ``"screen"``, ``null`` (default). -``dividers`` - Defines the dividers used in all Powerline extensions. This option - should usually only be changed if you don't have a patched font, or if - you use a font patched with the legacy font patcher. - - The ``hard`` dividers are used to divide segments with different - background colors, while the ``soft`` dividers are used to divide - segments with the same background color. - .. _config-common-paths: ``paths`` @@ -95,6 +86,12 @@ Common configuration is a subdictionary that is a value of ``common`` key in Boolean, determines whether configuration should be reloaded at all. Defaults to ``True``. +.. _config-common-default_top_theme: + +``default_top_theme`` + String, determines which top-level theme will be used as the default. + Defaults to ``powerline``. See `Themes`_ section for more details. + Extension-specific configuration -------------------------------- @@ -109,6 +106,12 @@ Common configuration is a subdictionary that is a value of ``ext`` key in Defines the theme used for this extension. +``top_theme`` + .. _config-ext-top_theme: + + Defines the top-level theme used for this extension. See `Themes`_ section + for more details. + ``local_themes`` .. _config-ext-local_themes: @@ -155,7 +158,7 @@ Colorschemes ============ :Location: :file:`powerline/colorschemes/{name}.json`, - :file:`powerline/colorscheme/__main__.json`, + :file:`powerline/colorschemes/__main__.json`, :file:`powerline/colorschemes/{extension}/{name}.json` Colorscheme files are processed in order given: definitions from each next file @@ -213,7 +216,17 @@ override those from each previous file. It is required that either Themes ====== -:Location: :file:`powerline/themes/{extension}/{name}.json` +:Location: :file:`powerline/themes/{top_theme}.json`, + :file:`powerline/themes/__main__.json`, + :file:`powerline/themes/{extension}/{name}.json` + +Theme files are processed in order given: definitions from each next file +override those from each previous file. It is required that file +:file:`powerline/themes/{extension}/{name}.json` exists. + +`{top_theme}` component of the file name is obtained either from :ref:`top_theme +extension-specific key ` or from :ref:`default_top_theme +common configuration key `. ``name`` Name of the theme. @@ -223,6 +236,20 @@ Themes ``default_module`` Python module where segments will be looked by default. +``spaces`` + Defines number of spaces just before the divider (on the right side) or just + after it (on the left side). These spaces will not be added if divider is + not drawn. + +``dividers`` + Defines the dividers used in all Powerline extensions. This option + should usually only be changed if you don't have a patched font, or if + you use a font patched with the legacy font patcher. + + The ``hard`` dividers are used to divide segments with different + background colors, while the ``soft`` dividers are used to divide + segments with the same background color. + .. _config-themes-segment_data: ``segment_data`` @@ -240,6 +267,9 @@ Themes `. For the :ref:`default theme ` itself step 2 is obviously avoided. + .. note:: Top-level themes are out of equation here: they are merged + before the above merging process happens. + ``segments`` A dict with a ``left`` and a ``right`` lists, consisting of segment dictionaries. Shell themes may also contain ``above`` list of dictionaries. diff --git a/powerline/__init__.py b/powerline/__init__.py index 207a24dd..08446057 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -215,6 +215,7 @@ def finish_common_config(common_config): paths. ''' common_config = common_config.copy() + common_config.setdefault('default_top_theme', 'powerline') common_config.setdefault('paths', []) common_config.setdefault('watcher', 'auto') common_config.setdefault('log_level', 'WARNING') @@ -345,13 +346,15 @@ class Powerline(object): if load_main: self._purge_configs('main') config = self.load_main_config() - self.common_config = config['common'] + self.common_config = finish_common_config(config['common']) if self.common_config != self.prev_common_config: common_config_differs = True - self.prev_common_config = self.common_config + load_theme = (load_theme + or not self.prev_common_config + or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme']) - self.common_config = finish_common_config(self.common_config) + self.prev_common_config = self.common_config self.import_paths = self.common_config['paths'] @@ -386,6 +389,8 @@ class Powerline(object): if interval is not None and not self.config_loader.is_alive(): self.config_loader.start() + self.default_top_theme = self.common_config['default_top_theme'] + self.ext_config = config['ext'][self.ext] if self.ext_config != self.prev_ext_config: ext_config_differs = True @@ -445,30 +450,20 @@ class Powerline(object): ''' return get_config_paths() - def _load_config(self, cfg_path, type): + def _load_config(self, cfg_path, cfg_type): '''Load configuration and setup watches.''' return load_config( cfg_path, self.find_config_files, self.config_loader, - self.cr_callbacks[type] + self.cr_callbacks[cfg_type] ) - def _purge_configs(self, type): - function = self.cr_callbacks[type] + def _purge_configs(self, cfg_type): + function = self.cr_callbacks[cfg_type] self.config_loader.unregister_functions(set((function,))) self.config_loader.unregister_missing(set(((self.find_config_files, function),))) - def load_theme_config(self, name): - '''Get theme configuration. - - :param str name: - Name of the theme to load. - - :return: dictionary with :ref:`theme configuration ` - ''' - return self._load_config(os.path.join('themes', self.ext, name), 'theme') - def load_main_config(self): '''Get top-level configuration. @@ -476,6 +471,47 @@ class Powerline(object): ''' return self._load_config('config', 'main') + def _load_hierarhical_config(self, cfg_type, levels, ignore_levels): + '''Load and merge multiple configuration files + + :param str cfg_type: + Type of the loaded configuration files (e.g. ``colorscheme``, + ``theme``). + :param list levels: + Configuration names resembling levels in hierarchy, sorted by + priority. Configuration file names with higher priority should go + last. + :param set ignore_levels: + If only files listed in this variable are present then configuration + file is considered not loaded: at least one file on the level not + listed in this variable must be present. + ''' + config = {} + loaded = 0 + exceptions = [] + for i, cfg_path in enumerate(levels): + try: + lvl_config = self._load_config(cfg_path, cfg_type) + except IOError as e: + if sys.version_info < (3,): + tb = sys.exc_info()[2] + exceptions.append((e, tb)) + else: + exceptions.append(e) + else: + if i not in ignore_levels: + loaded += 1 + mergedicts(config, lvl_config) + if not loaded: + for exception in exceptions: + if type(exception) is tuple: + e = exception[0] + else: + e = exception + self.exception('Failed to load %s: {0}' % cfg_type, e, exception=exception) + raise e + return config + def load_colorscheme_config(self, name): '''Get colorscheme. @@ -484,40 +520,27 @@ class Powerline(object): :return: dictionary with :ref:`colorscheme configuration `. ''' - # TODO Make sure no colorscheme name ends with __ (do it in - # powerline-lint) levels = ( os.path.join('colorschemes', name), os.path.join('colorschemes', self.ext, '__main__'), os.path.join('colorschemes', self.ext, name), ) - config = {} - loaded = 0 - exceptions = [] - for cfg_path in levels: - try: - lvl_config = self._load_config(cfg_path, 'colorscheme') - except IOError as e: - if sys.version_info < (3,): - tb = sys.exc_info()[2] - exceptions.append((e, tb)) - else: - exceptions.append(e) - else: - if not cfg_path.endswith('__'): - loaded += 1 - # TODO Either make sure `attr` list is always present or make - # mergedicts not merge group definitions. - mergedicts(config, lvl_config) - if not loaded: - for exception in exceptions: - if type(exception) is tuple: - e = exception[0] - else: - e = exception - self.exception('Failed to load colorscheme: {0}', e, exception=exception) - raise e - return config + return self._load_hierarhical_config('colorscheme', levels, (1,)) + + def load_theme_config(self, name): + '''Get theme configuration. + + :param str name: + Name of the theme to load. + + :return: dictionary with :ref:`theme configuration ` + ''' + levels = ( + os.path.join('themes', self.ext_config.get('top_theme') or self.default_top_theme), + os.path.join('themes', self.ext, '__main__'), + os.path.join('themes', self.ext, name), + ) + return self._load_hierarhical_config('theme', levels, (0, 1,)) def load_colors_config(self): '''Get colorscheme. diff --git a/powerline/config_files/config.json b/powerline/config_files/config.json index f64bdfc1..4968fc6b 100644 --- a/powerline/config_files/config.json +++ b/powerline/config_files/config.json @@ -1,17 +1,6 @@ { "common": { - "term_truecolor": false, - "dividers": { - "left": { - "hard": " ", - "soft": " " - }, - "right": { - "hard": " ", - "soft": " " - } - }, - "spaces": 1 + "term_truecolor": false }, "ext": { "ipython": { diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json new file mode 100644 index 00000000..ea29344e --- /dev/null +++ b/powerline/config_files/themes/powerline.json @@ -0,0 +1,13 @@ +{ + "dividers": { + "left": { + "hard": " ", + "soft": " " + }, + "right": { + "hard": " ", + "soft": " " + } + }, + "spaces": 1 +} diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 6af261c6..721d1a5a 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -431,9 +431,6 @@ class WithPath(object): def check_matcher_func(ext, match_name, data, context, echoerr): import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])] - if match_name == '__tabline__': - return True, False - match_module, separator, match_function = match_name.rpartition('.') if not separator: match_module = 'powerline.matchers.{0}'.format(ext) @@ -512,23 +509,30 @@ def check_config(d, theme, data, context, echoerr): return True, False, False +def check_top_theme(theme, data, context, echoerr): + if theme not in data['configs']['top_themes']: + echoerr(context='Error while checking extension configuration (key {key})'.format(key=context_key(context)), + context_mark=context[-2][0].mark, + problem='failed to find top theme {0}'.format(theme), + problem_mark=theme.mark) + return True, False, True + return True, False, False + + divider_spec = Spec().type(unicode).len('le', 3, lambda value: 'Divider {0!r} is too large!'.format(value)).copy -divside_spec = Spec( - hard=divider_spec(), - soft=divider_spec(), +ext_theme_spec = Spec().type(unicode).func(lambda *args: check_config('themes', *args)).copy +top_theme_spec = Spec().type(unicode).func(check_top_theme).copy +ext_spec = Spec( + colorscheme=Spec().type(unicode).func( + (lambda *args: check_config('colorschemes', *args)) + ), + theme=ext_theme_spec(), + top_theme=top_theme_spec().optional(), ).copy -colorscheme_spec = Spec().type(unicode).func(lambda *args: check_config('colorschemes', *args)).copy -theme_spec = Spec().type(unicode).func(lambda *args: check_config('themes', *args)).copy main_spec = (Spec( common=Spec( - dividers=Spec( - left=divside_spec(), - right=divside_spec(), - ), - spaces=Spec().unsigned().cmp( - 'le', 2, lambda value: 'Are you sure you need such a big ({0}) number of spaces?'.format(value) - ), + default_top_theme=top_theme_spec().optional(), term_truecolor=Spec().type(bool).optional(), # Python is capable of loading from zip archives. Thus checking path # only for existence of the path, not for it being a directory @@ -556,36 +560,29 @@ main_spec = (Spec( watcher=Spec().type(unicode).oneof(set(('auto', 'inotify', 'stat'))).optional(), ).context_message('Error while loading common configuration (key {key})'), ext=Spec( - vim=Spec( - colorscheme=colorscheme_spec(), - theme=theme_spec(), - local_themes=Spec().unknown_spec( - lambda *args: check_matcher_func('vim', *args), theme_spec() + vim=ext_spec().update( + local_themes=Spec( + __tabline__=ext_theme_spec(), + ).unknown_spec( + lambda *args: check_matcher_func('vim', *args), ext_theme_spec() ), ).optional(), - ipython=Spec( - colorscheme=colorscheme_spec(), - theme=theme_spec(), + ipython=ext_spec().update( local_themes=Spec( - in2=theme_spec(), - out=theme_spec(), - rewrite=theme_spec(), + in2=ext_theme_spec(), + out=ext_theme_spec(), + rewrite=ext_theme_spec(), ), ).optional(), - shell=Spec( - colorscheme=colorscheme_spec(), - theme=theme_spec(), + shell=ext_spec().update( local_themes=Spec( - continuation=theme_spec(), - select=theme_spec(), + continuation=ext_theme_spec(), + select=ext_theme_spec(), ), ).optional(), ).unknown_spec( check_ext, - Spec( - colorscheme=colorscheme_spec(), - theme=theme_spec(), - ) + ext_spec(), ).context_message('Error while loading extensions configuration (key {key})'), ).context_message('Error while loading main configuration')) @@ -771,7 +768,7 @@ type_keys = { } required_keys = { 'function': set(('name',)), - 'string': set(('contents',)), + 'string': set(()), 'filler': set(), 'segment_list': set(('name', 'segments',)), } @@ -845,11 +842,11 @@ def check_full_segment_data(segment, data, context, echoerr): ext = data['ext'] theme_segment_data = context[0][1].get('segment_data', {}) - top_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None) - if not top_theme_name or data['theme'] == top_theme_name: + main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None) + if not main_theme_name or data['theme'] == main_theme_name: top_segment_data = {} else: - top_segment_data = data['ext_theme_configs'].get(top_theme_name, {}).get('segment_data', {}) + top_segment_data = data['ext_theme_configs'].get(main_theme_name, {}).get('segment_data', {}) names = [segment['name']] if segment.get('type', 'function') == 'function': @@ -977,12 +974,16 @@ def check_segment_name(name, data, context, echoerr): return True, False, hadproblem elif context[-2][1].get('type') != 'segment_list': if name not in context[0][1].get('segment_data', {}): - top_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None) - if data['theme'] == top_theme_name: - top_theme = {} + main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None) + if data['theme'] == main_theme_name: + main_theme = {} else: - top_theme = data['ext_theme_configs'].get(top_theme_name, {}) - if name not in top_theme.get('segment_data', {}): + main_theme = data['ext_theme_configs'].get(main_theme_name, {}) + if ( + name not in main_theme.get('segment_data', {}) + and name not in data['ext_theme_configs'].get('__main__', {}).get('segment_data', {}) + and not any(((name in theme.get('segment_data', {})) for theme in data['top_themes'].values())) + ): echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), problem='found useless use of name key (such name is not present in theme/segment_data)', problem_mark=name.mark) @@ -1070,34 +1071,49 @@ def check_highlight_groups(hl_groups, data, context, echoerr): return True, False, False -def check_segment_data_key(key, data, context, echoerr): +def list_themes(data, context): + theme_type = data['theme_type'] ext = data['ext'] - top_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None) - is_top_theme = (data['theme'] == top_theme_name) - if is_top_theme: - themes = data['ext_theme_configs'].values() + main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None) + is_main_theme = (data['theme'] == main_theme_name) + if theme_type == 'top': + return list(itertools.chain(*[ + [(ext, theme) for theme in theme_configs.values()] + for ext, theme_configs in data['theme_configs'].items() + ])) + elif theme_type == 'main' or is_main_theme: + return [(ext, theme) for theme in data['ext_theme_configs'].values()] else: - themes = [context[0][1]] + return [(ext, context[0][1])] - for theme in themes: + +def check_segment_data_key(key, data, context, echoerr): + has_module_name = '.' in key + found = False + for ext, theme in list_themes(data, context): for segments in theme.get('segments', {}).values(): - found = False for segment in segments: if 'name' in segment: - if key == segment['name']: - found = True - module = segment.get('module', theme.get('default_module', 'powerline.segments.' + ext)) - if key == unicode(module) + '.' + unicode(segment['name']): - found = True + if has_module_name: + module = segment.get('module', theme.get('default_module', 'powerline.segments.' + ext)) + full_name = unicode(module) + '.' + unicode(segment['name']) + if key == full_name: + found = True + break + else: + if key == segment['name']: + found = True + break if found: break if found: break else: - echoerr(context='Error while checking segment data', - problem='found key {0} that cannot be associated with any segment'.format(key), - problem_mark=key.mark) - return True, False, True + if data['theme_type'] != 'top': + echoerr(context='Error while checking segment data', + problem='found key {0} that cannot be associated with any segment'.format(key), + problem_mark=key.mark) + return True, False, True return True, False, False @@ -1109,8 +1125,8 @@ threaded_args_specs = { } -def check_args_variant(segment, args, data, context, echoerr): - argspec = getconfigargspec(segment) +def check_args_variant(func, args, data, context, echoerr): + argspec = getconfigargspec(func) present_args = set(args) all_args = set(argspec.args) required_args = set(argspec.args[:-len(argspec.defaults)]) @@ -1132,7 +1148,7 @@ def check_args_variant(segment, args, data, context, echoerr): problem_mark=next(iter(present_args - all_args)).mark) hadproblem = True - if isinstance(segment, ThreadedSegment): + if isinstance(func, ThreadedSegment): for key in set(threaded_args_specs) & present_args: proceed, khadproblem = threaded_args_specs[key].match( args[key], @@ -1149,13 +1165,13 @@ def check_args_variant(segment, args, data, context, echoerr): return hadproblem -def check_args(get_segment_variants, args, data, context, echoerr): +def check_args(get_functions, args, data, context, echoerr): new_echoerr = DelayedEchoErr(echoerr) count = 0 hadproblem = False - for segment in get_segment_variants(data, context, new_echoerr): + for func in get_functions(data, context, new_echoerr): count += 1 - shadproblem = check_args_variant(segment, args, data, context, echoerr) + shadproblem = check_args_variant(func, args, data, context, echoerr) if shadproblem: hadproblem = True @@ -1171,7 +1187,7 @@ def check_args(get_segment_variants, args, data, context, echoerr): return True, False, hadproblem -def get_one_segment_variant(data, context, echoerr): +def get_one_segment_function(data, context, echoerr): name = context[-2][1].get('name') if name: func = import_segment(name, data, context, echoerr) @@ -1179,7 +1195,7 @@ def get_one_segment_variant(data, context, echoerr): yield func -def get_all_possible_segments(data, context, echoerr): +def get_all_possible_functions(data, context, echoerr): name = context[-2][0] module, name = name.rpartition('.')[::2] if module: @@ -1187,13 +1203,13 @@ def get_all_possible_segments(data, context, echoerr): if func: yield func else: - for theme_config in data['ext_theme_configs'].values(): + for ext, theme_config in list_themes(data, context): for segments in theme_config.get('segments', {}).values(): for segment in segments: if segment.get('type', 'function') == 'function': module = segment.get( 'module', - context[0][1].get('default_module', 'powerline.segments.' + data['ext']) + theme_config.get('default_module', 'powerline.segments.' + data['ext']) ) func = import_segment(name, data, context, echoerr, module=module) if func: @@ -1222,7 +1238,7 @@ segment_spec = Spec( before=Spec().type(unicode).optional(), width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(), align=Spec().oneof(set('lr')).optional(), - args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_variant, *args, **kwargs)), + args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_function, *args, **kwargs)), contents=Spec().type(unicode).optional(), highlight_group=Spec().list( highlight_group_spec().re( @@ -1245,20 +1261,52 @@ segdict_spec=Spec( (lambda value, *args: (True, True, not (('left' in value) or ('right' in value)))), (lambda value: 'segments dictionary must contain either left, right or both keys') ).context_message('Error while loading segments (key {key})').copy -theme_spec = (Spec( - default_module=segment_module_spec(), +divside_spec = Spec( + hard=divider_spec(), + soft=divider_spec(), +).copy +segment_data_value_spec = Spec( + after=Spec().type(unicode).optional(), + before=Spec().type(unicode).optional(), + display=Spec().type(bool).optional(), + args=args_spec().func(lambda *args, **kwargs: check_args(get_all_possible_functions, *args, **kwargs)), + contents=Spec().type(unicode).optional(), +).copy +dividers_spec = Spec( + left=divside_spec(), + right=divside_spec(), +).copy +spaces_spec = Spec().unsigned().cmp( + 'le', 2, (lambda value: 'Are you sure you need such a big ({0}) number of spaces?'.format(value)) +).copy +common_theme_spec = Spec( + default_module=segment_module_spec().optional(), +).context_message('Error while loading theme').copy +top_theme_spec = common_theme_spec().update( + dividers=dividers_spec(), + spaces=spaces_spec(), segment_data=Spec().unknown_spec( Spec().func(check_segment_data_key), - Spec( - after=Spec().type(unicode).optional(), - before=Spec().type(unicode).optional(), - display=Spec().type(bool).optional(), - args=args_spec().func(lambda *args, **kwargs: check_args(get_all_possible_segments, *args, **kwargs)), - contents=Spec().type(unicode).optional(), - ), + segment_data_value_spec(), + ).optional().context_message('Error while loading segment data (key {key})'), +) +main_theme_spec = common_theme_spec().update( + dividers=dividers_spec().optional(), + spaces=spaces_spec().optional(), + segment_data=Spec().unknown_spec( + Spec().func(check_segment_data_key), + segment_data_value_spec(), + ).optional().context_message('Error while loading segment data (key {key})'), +) +theme_spec = common_theme_spec().update( + dividers=dividers_spec().optional(), + spaces=spaces_spec().optional(), + segment_data=Spec().unknown_spec( + Spec().func(check_segment_data_key), + segment_data_value_spec(), ).optional().context_message('Error while loading segment data (key {key})'), segments=segdict_spec().update(above=Spec().list(segdict_spec()).optional()), -).context_message('Error while loading theme')) +) def generate_json_config_loader(lhadproblem): @@ -1315,6 +1363,8 @@ def check(paths=None, debug=False): hadproblem = True sys.stderr.write('Path {0} is supposed to be a directory, but it is not\n'.format(d)) + hadproblem = False + configs = defaultdict(lambda: defaultdict(lambda: {})) for typ in ('themes', 'colorschemes'): for ext in paths[typ]: @@ -1324,6 +1374,11 @@ def check(paths=None, debug=False): name = subp[:-5] if name != '__main__': lists[typ].add(name) + if name.startswith('__') or name.endswith('__'): + hadproblem = True + sys.stderr.write('File name is not supposed to start or end with “__”: {0}'.format( + os.path.join(d, subp) + )) configs[typ][ext][name] = os.path.join(d, subp) for path in paths['top_' + typ]: name = os.path.basename(path)[:-5] @@ -1341,7 +1396,6 @@ def check(paths=None, debug=False): typ, )) - hadproblem = False try: main_config = load_config('config', find_config_files, config_loader) except IOError: @@ -1469,17 +1523,53 @@ def check(paths=None, debug=False): hadproblem = True theme_configs[ext][theme] = config + top_theme_configs = {} + for top_theme, top_theme_file in configs['top_themes'].items(): + with open_file(top_theme_file) as config_file_fp: + try: + config, lhadproblem = load(config_file_fp) + except MarkedError as e: + sys.stderr.write(str(e) + '\n') + hadproblem = True + continue + if lhadproblem: + hadproblem = True + top_theme_configs[top_theme] = config + for ext, configs in theme_configs.items(): data = { 'ext': ext, 'colorscheme_configs': colorscheme_configs, 'import_paths': import_paths, 'main_config': main_config, + 'top_themes': top_theme_configs, 'ext_theme_configs': configs, 'colors_config': colors_config } for theme, config in configs.items(): data['theme'] = theme - if theme_spec.match(config, context=(('', config),), data=data, echoerr=ee)[1]: + if theme == '__main__': + data['theme_type'] = 'main' + spec = main_theme_spec + else: + data['theme_type'] = 'regular' + spec = theme_spec + if spec.match(config, context=(('', config),), data=data, echoerr=ee)[1]: hadproblem = True + + for top_theme, config in top_theme_configs.items(): + data = { + 'ext': ext, + 'colorscheme_configs': colorscheme_configs, + 'import_paths': import_paths, + 'main_config': main_config, + 'theme_configs': theme_configs, + 'ext_theme_configs': configs, + 'colors_config': colors_config + } + data['theme_type'] = 'top' + data['theme'] = top_theme + if top_theme_spec.match(config, context=(('', config),), data=data, echoerr=ee)[1]: + hadproblem = True + return hadproblem diff --git a/powerline/theme.py b/powerline/theme.py index 25fa7e45..9c61ddc7 100644 --- a/powerline/theme.py +++ b/powerline/theme.py @@ -31,13 +31,13 @@ class Theme(object): top_theme_config=None, run_once=False, shutdown_event=None): - self.dividers = theme_config.get('dividers', common_config['dividers']) + self.dividers = theme_config['dividers'] self.dividers = dict(( (key, dict((k, u(v)) for k, v in val.items())) for key, val in self.dividers.items() )) - self.spaces = theme_config.get('spaces', common_config['spaces']) + self.spaces = theme_config['spaces'] self.segments = [] self.EMPTY_SEGMENT = { 'contents': None, diff --git a/tests/test_config_merging.py b/tests/test_config_merging.py index 7941e148..5249a0a3 100644 --- a/tests/test_config_merging.py +++ b/tests/test_config_merging.py @@ -17,17 +17,6 @@ CONFIG_DIR = 'tests/config' root_config = lambda: { 'common': { - 'dividers': { - 'left': { - 'hard': '#>', - 'soft': '|>', - }, - 'right': { - 'hard': '<#', - 'soft': '<|', - }, - }, - 'spaces': 0, 'interval': None, 'watcher': 'auto', }, @@ -76,12 +65,41 @@ theme_config = lambda: { } } +top_theme_config = lambda: { + 'dividers': { + 'left': { + 'hard': '#>', + 'soft': '|>', + }, + 'right': { + 'hard': '<#', + 'soft': '<|', + }, + }, + 'spaces': 0, +} + main_tree = lambda: { '1/config': root_config(), '1/colors': colors_config(), '1/colorschemes/default': colorscheme_config(), '1/themes/test/default': theme_config(), + '1/themes/powerline': top_theme_config(), + '1/themes/other1': mdc(top_theme_config(), { + 'dividers': { + 'left': { + 'hard': '!>', + } + } + }), + '1/themes/other2': mdc(top_theme_config(), { + 'dividers': { + 'left': { + 'hard': '>>', + } + } + }), } @@ -151,11 +169,7 @@ class TestMerging(TestCase): with WithConfigTree(mdc(main_tree(), { '2/config': { 'common': { - 'dividers': { - 'left': { - 'hard': '!>', - } - } + 'default_top_theme': 'other1', } }, })) as p: @@ -163,36 +177,26 @@ class TestMerging(TestCase): with WithConfigTree(mdc(main_tree(), { '2/config': { 'common': { - 'dividers': { - 'left': { - 'hard': '!>', - } - } + 'default_top_theme': 'other1', } }, '3/config': { 'common': { - 'dividers': { - 'left': { - 'hard': '>>', - } - } + 'default_top_theme': 'other2', } }, })) as p: self.assertRenderEqual(p, '{12} bt{2-}>>{--}') + + def test_top_theme_merging(self): with WithConfigTree(mdc(main_tree(), { - '2/config': { - 'common': { - 'spaces': 1, - } + '2/themes/powerline': { + 'spaces': 1, }, - '3/config': { - 'common': { - 'dividers': { - 'left': { - 'hard': '>>', - } + '3/themes/powerline': { + 'dividers': { + 'left': { + 'hard': '>>', } } }, diff --git a/tests/test_config_reload.py b/tests/test_config_reload.py index 8deb0d1a..eb7e1cb9 100644 --- a/tests/test_config_reload.py +++ b/tests/test_config_reload.py @@ -12,17 +12,6 @@ from tests.lib.config_mock import get_powerline, add_watcher_events config = { 'config': { 'common': { - 'dividers': { - "left": { - "hard": ">>", - "soft": ">", - }, - "right": { - "hard": "<<", - "soft": "<", - }, - }, - 'spaces': 0, 'interval': 0, 'watcher': 'test', }, @@ -73,6 +62,32 @@ config = { ], }, }, + 'themes/powerline': { + 'dividers': { + "left": { + "hard": ">>", + "soft": ">", + }, + "right": { + "hard": "<<", + "soft": "<", + }, + }, + 'spaces': 0, + }, + 'themes/other': { + 'dividers': { + "left": { + "hard": ">>", + "soft": ">", + }, + "right": { + "hard": "<<", + "soft": "<", + }, + }, + 'spaces': 1, + }, 'themes/test/2': { 'segments': { "left": [ @@ -116,7 +131,7 @@ class TestConfigReload(TestCase): def test_noreload(self, config): with get_powerline(config, run_once=True) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') config['config']['common']['spaces'] = 1 add_watcher_events(p, 'config', wait=False, interval=0.05) # When running once thread should not start @@ -128,25 +143,30 @@ class TestConfigReload(TestCase): def test_reload_main(self, config): with get_powerline(config, run_once=False) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') - config['config']['common']['spaces'] = 1 + config['config']['common']['default_top_theme'] = 'other' add_watcher_events(p, 'config') + p.render() self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') - self.assertAccessEvents(p, 'config') + self.assertAccessEvents(p, 'config', 'themes/other', 'check:themes/test/__main__', 'themes/test/default') self.assertEqual(p.logger._pop_msgs(), []) config['config']['ext']['test']['theme'] = 'nonexistent' add_watcher_events(p, 'config') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') - self.assertAccessEvents(p, 'config', 'check:themes/test/nonexistent') + self.assertAccessEvents(p, 'config', 'check:themes/test/nonexistent', 'themes/other', 'check:themes/test/__main__') # It should normally handle file missing error - self.assertEqual(p.logger._pop_msgs(), ['exception:test:powerline:Failed to create renderer: themes/test/nonexistent']) + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/test/__main__', + 'exception:test:powerline:Failed to load theme: themes/test/nonexistent', + 'exception:test:powerline:Failed to create renderer: themes/test/nonexistent' + ]) config['config']['ext']['test']['theme'] = 'default' add_watcher_events(p, 'config') self.assertEqual(p.render(), '<1 2 1> s <2 4 False>>><3 4 4>g <4 False False>>>') - self.assertAccessEvents(p, 'config', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'themes/test/default', 'themes/other', 'check:themes/test/__main__') self.assertEqual(p.logger._pop_msgs(), []) config['config']['ext']['test']['colorscheme'] = 'nonexistent' @@ -170,7 +190,7 @@ class TestConfigReload(TestCase): config['config']['ext']['test']['theme'] = '2' add_watcher_events(p, 'config') self.assertEqual(p.render(), '<2 3 1> t <3 4 False>>><1 4 4>b <4 False False>>>') - self.assertAccessEvents(p, 'config', 'themes/test/2') + self.assertAccessEvents(p, 'config', 'themes/test/2', 'themes/other', 'check:themes/test/__main__') self.assertEqual(p.logger._pop_msgs(), []) self.assertEqual(p.renderer.local_themes, None) @@ -185,7 +205,7 @@ class TestConfigReload(TestCase): def test_reload_unexistent(self, config): with get_powerline(config, run_once=False) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') config['config']['ext']['test']['colorscheme'] = 'nonexistentraise' add_watcher_events(p, 'config') @@ -222,7 +242,7 @@ class TestConfigReload(TestCase): def test_reload_colors(self, config): with get_powerline(config, run_once=False) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') config['colors']['colors']['col1'] = 5 add_watcher_events(p, 'colors') @@ -234,7 +254,7 @@ class TestConfigReload(TestCase): def test_reload_colorscheme(self, config): with get_powerline(config, run_once=False) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') config['colorschemes/test/default']['groups']['str1']['bg'] = 'col3' add_watcher_events(p, 'colorschemes/test/default') @@ -246,12 +266,24 @@ class TestConfigReload(TestCase): def test_reload_theme(self, config): with get_powerline(config, run_once=False) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' add_watcher_events(p, 'themes/test/default') self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'themes/test/default') + self.assertAccessEvents(p, 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') + self.assertEqual(p.logger._pop_msgs(), []) + + @with_new_config + def test_reload_top_theme(self, config): + with get_powerline(config, run_once=False) as p: + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') + + config['themes/powerline']['dividers']['left']['hard'] = '|>' + add_watcher_events(p, 'themes/powerline') + self.assertEqual(p.render(), '<1 2 1> s<2 4 False>|><3 4 4>g<4 False False>|>') + self.assertAccessEvents(p, 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') self.assertEqual(p.logger._pop_msgs(), []) @with_new_config @@ -259,12 +291,12 @@ class TestConfigReload(TestCase): config['config']['common']['interval'] = None with get_powerline(config, run_once=False) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' add_watcher_events(p, 'themes/test/default', wait=False) self.assertEqual(p.render(), '<1 2 1> col3<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'themes/test/default') + self.assertAccessEvents(p, 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') self.assertEqual(p.logger._pop_msgs(), []) self.assertTrue(p._watcher._calls) @@ -273,7 +305,7 @@ class TestConfigReload(TestCase): config['config']['common']['interval'] = None with get_powerline(config, run_once=True) as p: self.assertEqual(p.render(), '<1 2 1> s<2 4 False>>><3 4 4>g<4 False False>>>') - self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default') + self.assertAccessEvents(p, 'config', 'colors', 'check:colorschemes/default', 'check:colorschemes/test/__main__', 'colorschemes/test/default', 'themes/test/default', 'themes/powerline', 'check:themes/test/__main__') config['themes/test/default']['segments']['left'][0]['contents'] = 'col3' add_watcher_events(p, 'themes/test/default', wait=False) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index a1d6b836..6a64ce36 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -22,17 +22,6 @@ def highlighted_string(s, group, **kwargs): config = { 'config': { 'common': { - 'dividers': { - 'left': { - 'hard': '>>', - 'soft': '>', - }, - 'right': { - 'hard': '<<', - 'soft': '<', - }, - }, - 'spaces': 0, 'interval': 0, 'watcher': 'test', }, @@ -104,6 +93,26 @@ config = { ], }, }, + 'themes/powerline': { + 'dividers': { + 'left': { + 'hard': '>>', + 'soft': '>', + }, + 'right': { + 'hard': '<<', + 'soft': '<', + }, + }, + 'spaces': 0, + }, + 'themes/test/__main__': { + 'dividers': { + 'right': { + 'soft': '|', + }, + }, + }, 'themes/vim/default': { 'default_module': 'powerline.segments.common', 'segments': { @@ -147,9 +156,9 @@ class TestRender(TestCase): class TestLines(TestRender): @add_args def test_without_above(self, p, config): - self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}<{344}f {--}') - self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}<{344}f {--}', width=10) - # self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}<{344} f {--}', width=11) + self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}|{344}f {--}') + self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', width=10) + # self.assertRenderEqual(p, '{121} s {24}>>{344}g{34}>{34}|{344} f {--}', width=11) self.assertEqual(list(p.render_above_lines()), []) @with_new_config @@ -158,21 +167,21 @@ class TestLines(TestRender): config['themes/test/default']['segments']['above'] = [old_segments] with get_powerline(config, run_once=True, simpler_renderer=True) as p: self.assertRenderLinesEqual(p, [ - '{121} s{24}>>{344}g{34}>{34}<{344}f {--}', + '{121} s{24}>>{344}g{34}>{34}|{344}f {--}', ]) self.assertRenderLinesEqual(p, [ - '{121} s {24}>>{344}g{34}>{34}<{344}f {--}', + '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', ], width=10) config['themes/test/default']['segments']['above'] = [old_segments] * 2 with get_powerline(config, run_once=True, simpler_renderer=True) as p: self.assertRenderLinesEqual(p, [ - '{121} s{24}>>{344}g{34}>{34}<{344}f {--}', - '{121} s{24}>>{344}g{34}>{34}<{344}f {--}', + '{121} s{24}>>{344}g{34}>{34}|{344}f {--}', + '{121} s{24}>>{344}g{34}>{34}|{344}f {--}', ]) self.assertRenderLinesEqual(p, [ - '{121} s {24}>>{344}g{34}>{34}<{344}f {--}', - '{121} s {24}>>{344}g{34}>{34}<{344}f {--}', + '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', + '{121} s {24}>>{344}g{34}>{34}|{344}f {--}', ], width=10) @@ -299,6 +308,82 @@ class TestColorschemesHierarchy(TestRender): self.assertEqual(p.logger._pop_msgs(), []) +class TestThemeHierarchy(TestRender): + @add_args + def test_hierarchy(self, p, config): + self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}|{344}f {--}') + + @add_args + def test_no_main(self, p, config): + del config['themes/test/__main__'] + self.assertRenderEqual(p, '{121} s{24}>>{344}g{34}>{34}<{344}f {--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_no_powerline(self, p, config): + config['themes/test/__main__']['dividers'] = config['themes/powerline']['dividers'] + config['themes/test/__main__']['spaces'] = 1 + del config['themes/powerline'] + self.assertRenderEqual(p, '{121} s {24}>>{344}g {34}>{34}<{344} f {--}') + self.assertEqual(p.logger._pop_msgs(), []) + + @add_args + def test_no_default(self, p, config): + del config['themes/test/default'] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + @add_args + def test_only_default(self, p, config): + config['themes/test/default']['dividers'] = config['themes/powerline']['dividers'] + config['themes/test/default']['spaces'] = 1 + del config['themes/test/__main__'] + del config['themes/powerline'] + self.assertRenderEqual(p, '{121} s {24}>>{344}g {34}>{34}<{344} f {--}') + + @add_args + def test_only_main(self, p, config): + del config['themes/test/default'] + del config['themes/powerline'] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/powerline', + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + @add_args + def test_only_powerline(self, p, config): + del config['themes/test/default'] + del config['themes/test/__main__'] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/test/__main__', + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + @add_args + def test_nothing(self, p, config): + del config['themes/test/default'] + del config['themes/powerline'] + del config['themes/test/__main__'] + self.assertRenderEqual(p, 'themes/test/default') + self.assertEqual(p.logger._pop_msgs(), [ + 'exception:test:powerline:Failed to load theme: themes/powerline', + 'exception:test:powerline:Failed to load theme: themes/test/__main__', + 'exception:test:powerline:Failed to load theme: themes/test/default', + 'exception:test:powerline:Failed to create renderer: themes/test/default', + 'exception:test:powerline:Failed to render: themes/test/default', + ]) + + class TestVim(TestCase): def test_environ_update(self): # Regression test: test that segment obtains environment from vim, not From 37b1f967a23178d2d7bd819706ff9bf4f19d02f7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 5 Aug 2014 22:55:01 +0400 Subject: [PATCH 03/16] Move all unicode characters from themes to powerline.json --- powerline/config_files/themes/powerline.json | 33 ++++++++++++++++++- .../config_files/themes/shell/__main__.json | 14 ++++++++ .../config_files/themes/shell/default.json | 19 +---------- .../themes/shell/default_leftonly.json | 19 +---------- .../config_files/themes/tmux/default.json | 11 ++----- .../config_files/themes/vim/__main__.json | 10 ++++++ .../config_files/themes/vim/default.json | 15 --------- powerline/config_files/themes/wm/default.json | 7 ++-- 8 files changed, 63 insertions(+), 65 deletions(-) create mode 100644 powerline/config_files/themes/shell/__main__.json create mode 100644 powerline/config_files/themes/vim/__main__.json diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json index ea29344e..40cd0d6e 100644 --- a/powerline/config_files/themes/powerline.json +++ b/powerline/config_files/themes/powerline.json @@ -9,5 +9,36 @@ "soft": " " } }, - "spaces": 1 + "spaces": 1, + "segment_data": { + "branch": { + "before": " " + }, + + "line_current_symbol": { + "contents": " " + }, + + "powerline.segments.common.uptime": { + "before": "⇑ " + }, + "powerline.segments.common.date": { + "before": "⌚ " + }, + "powerline.segments.common.email_imap_alert": { + "before": "✉ " + }, + "powerline.segments.common.virtualenv": { + "before": "ⓔ " + }, + "powerline.segments.common.hostname": { + "before": " " + }, + + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + } + } } diff --git a/powerline/config_files/themes/shell/__main__.json b/powerline/config_files/themes/shell/__main__.json new file mode 100644 index 00000000..13ae942b --- /dev/null +++ b/powerline/config_files/themes/shell/__main__.json @@ -0,0 +1,14 @@ +{ + "segment_data": { + "hostname": { + "args": { + "only_if_ssh": true + } + }, + "cwd": { + "args": { + "dir_limit_depth": 3 + } + } + } +} diff --git a/powerline/config_files/themes/shell/default.json b/powerline/config_files/themes/shell/default.json index 35e8e18b..25867a30 100644 --- a/powerline/config_files/themes/shell/default.json +++ b/powerline/config_files/themes/shell/default.json @@ -1,19 +1,5 @@ { "default_module": "powerline.segments.common", - "segment_data": { - "hostname": { - "before": " ", - "args": { - "only_if_ssh": true - } - }, - "virtualenv": { - "before": "ⓔ " - }, - "branch": { - "before": " " - } - }, "segments": { "left": [ { @@ -30,10 +16,7 @@ "name": "virtualenv" }, { - "name": "cwd", - "args": { - "dir_limit_depth": 3 - } + "name": "cwd" }, { "module": "powerline.segments.shell", diff --git a/powerline/config_files/themes/shell/default_leftonly.json b/powerline/config_files/themes/shell/default_leftonly.json index 5060e1ea..d6c49de1 100644 --- a/powerline/config_files/themes/shell/default_leftonly.json +++ b/powerline/config_files/themes/shell/default_leftonly.json @@ -1,19 +1,5 @@ { "default_module": "powerline.segments.common", - "segment_data": { - "hostname": { - "before": " ", - "args": { - "only_if_ssh": true - } - }, - "virtualenv": { - "before": "ⓔ " - }, - "branch": { - "before": " " - } - }, "segments": { "left": [ { @@ -29,10 +15,7 @@ "name": "branch" }, { - "name": "cwd", - "args": { - "dir_limit_depth": 3 - } + "name": "cwd" }, { "module": "powerline.segments.shell", diff --git a/powerline/config_files/themes/tmux/default.json b/powerline/config_files/themes/tmux/default.json index ecf6fbb1..479506ae 100644 --- a/powerline/config_files/themes/tmux/default.json +++ b/powerline/config_files/themes/tmux/default.json @@ -1,13 +1,5 @@ { "default_module": "powerline.segments.common", - "segment_data": { - "uptime": { - "before": "⇑ " - }, - "date": { - "before": "⌚ " - } - }, "segments": { "right": [ { @@ -19,7 +11,8 @@ "priority": 50 }, { - "name": "date" + "name": "date", + "before": "" }, { "name": "date", diff --git a/powerline/config_files/themes/vim/__main__.json b/powerline/config_files/themes/vim/__main__.json new file mode 100644 index 00000000..7cd33055 --- /dev/null +++ b/powerline/config_files/themes/vim/__main__.json @@ -0,0 +1,10 @@ +{ + "segment_data": { + "line_percent": { + "args": { + "gradient": true + }, + "after": "%" + } + } +} diff --git a/powerline/config_files/themes/vim/default.json b/powerline/config_files/themes/vim/default.json index bcd65016..3998e187 100644 --- a/powerline/config_files/themes/vim/default.json +++ b/powerline/config_files/themes/vim/default.json @@ -1,19 +1,4 @@ { - "segment_data": { - "branch": { - "before": " " - }, - "modified_indicator": { - "args": { "text": "+" } - }, - "line_percent": { - "args": { "gradient": true }, - "after": "%" - }, - "line_current_symbol": { - "contents": " " - } - }, "segments": { "left": [ { diff --git a/powerline/config_files/themes/wm/default.json b/powerline/config_files/themes/wm/default.json index c1cee4b7..b00699a5 100644 --- a/powerline/config_files/themes/wm/default.json +++ b/powerline/config_files/themes/wm/default.json @@ -7,19 +7,18 @@ "priority": 50 }, { - "name": "date" + "name": "date", + "before": "" }, { "name": "date", "args": { "format": "%H:%M", "istime": true - }, - "before": "⌚ " + } }, { "name": "email_imap_alert", - "before": "✉ ", "priority": 10, "args": { "username": "", From 759b42a823527a5f33bd8a0d8cac71bc8d383d13 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 5 Aug 2014 23:24:16 +0400 Subject: [PATCH 04/16] Make now_playing segment accept state symbols from arguments --- powerline/segments/common.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/powerline/segments/common.py b/powerline/segments/common.py index 79b24c52..a0e54492 100644 --- a/powerline/segments/common.py +++ b/powerline/segments/common.py @@ -896,29 +896,30 @@ Highlight groups used: ``email_alert_gradient`` (gradient), ``email_alert``. ''') -class NowPlayingSegment(object): - STATE_SYMBOLS = { - 'fallback': '♫', - 'play': '▶', - 'pause': '▮▮', - 'stop': '■', - } +STATE_SYMBOLS = { + 'fallback': '♫', + 'play': '▶', + 'pause': '▮▮', + 'stop': '■', +} - def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', **kwargs): + +class NowPlayingSegment(object): + def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs): player_func = getattr(self, 'player_{0}'.format(player)) stats = { - 'state': None, - 'state_symbol': self.STATE_SYMBOLS['fallback'], + 'state': 'fallback', 'album': None, 'artist': None, 'title': None, 'elapsed': None, 'total': None, } - func_stats = player_func(**kwargs) + func_stats = player_func(state_symbol=state_symbols, **kwargs) if not func_stats: return None stats.update(func_stats) + stats['state_symbol'] = state_symbols.get(stats['state']) return format.format(**stats) @staticmethod @@ -965,7 +966,6 @@ class NowPlayingSegment(object): state = self._convert_state(now_playing.get('status')) return { 'state': state, - 'state_symbol': self.STATE_SYMBOLS.get(state), 'album': now_playing.get('album'), 'artist': now_playing.get('artist'), 'title': now_playing.get('title'), @@ -998,7 +998,6 @@ class NowPlayingSegment(object): client.disconnect() return { 'state': status.get('state'), - 'state_symbol': self.STATE_SYMBOLS.get(status.get('state')), 'album': now_playing.get('album'), 'artist': now_playing.get('artist'), 'title': now_playing.get('title'), @@ -1027,7 +1026,6 @@ class NowPlayingSegment(object): state = self._convert_state(status) return { 'state': state, - 'state_symbol': self.STATE_SYMBOLS.get(state), 'album': info.get('xesam:album'), 'artist': info.get('xesam:artist')[0], 'title': info.get('xesam:title'), @@ -1074,7 +1072,6 @@ class NowPlayingSegment(object): return None return { 'state': state, - 'state_symbol': self.STATE_SYMBOLS.get(state), 'album': spotify_status[1], 'artist': spotify_status[2], 'title': spotify_status[3], From b0093c6b6716d44b6dc8c2cb0488ea874268d6ff Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 5 Aug 2014 23:43:31 +0400 Subject: [PATCH 05/16] =?UTF-8?q?Implement=20single-level=20merging=20of?= =?UTF-8?q?=20=E2=80=9Cargs=E2=80=9D=20dictionaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/configuration/reference.rst | 11 ++- .../config_files/themes/shell/__main__.json | 2 +- powerline/segment.py | 70 ++++++++++++++----- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index 6b899ea7..6722865f 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -256,11 +256,16 @@ common configuration key `. A dict where keys are segment names or strings ``{module}.{name}``. Used to specify default values for various keys: :ref:`after `, - :ref:`args ` (only for function segments), :ref:`before `, :ref:`contents ` (only for string segments if :ref:`name ` is defined), :ref:`display `. + + Key :ref:`args ` (only for function and + segments_list segments) is handled specially: unlike other values it is + merged with all other values, except that a single ``{module}.{name}`` key + if found prevents merging all ``{name}`` values. + When using :ref:`local themes ` values of these keys are first searched in the segment description, then in ``segment_data`` key of a local theme, then in ``segment_data`` key of a :ref:`default theme @@ -289,8 +294,8 @@ common configuration key `. Each segment dictionary has the following options: ``type`` - The segment type. Can be one of ``function`` (default), ``string`` - or ``filler``: + The segment type. Can be one of ``function`` (default), ``string``, + ``filler`` or ``segments_list``: ``function`` The segment contents is the return value of the function defined diff --git a/powerline/config_files/themes/shell/__main__.json b/powerline/config_files/themes/shell/__main__.json index 13ae942b..2b37f8b6 100644 --- a/powerline/config_files/themes/shell/__main__.json +++ b/powerline/config_files/themes/shell/__main__.json @@ -5,7 +5,7 @@ "only_if_ssh": true } }, - "cwd": { + "powerline.segments.common.cwd": { "args": { "dir_limit_depth": 3 } diff --git a/powerline/segment.py b/powerline/segment.py index 9ae5f0fe..8d5a956c 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -6,20 +6,52 @@ from powerline.lib.file_watcher import create_file_watcher import sys -def get_segment_key(segment, theme_configs, key, module=None, default=None): +def list_segment_key_values(segment, theme_configs, key, module=None, default=None): try: - return segment[key] + yield segment[key] except KeyError: - if 'name' in segment: - name = segment['name'] - for theme_config in theme_configs: - if 'segment_data' in theme_config: - for segment_key in ((module + '.' + name, name) if module else (name,)): - try: - return theme_config['segment_data'][segment_key][key] - except KeyError: - pass - return default + pass + try: + name = segment['name'] + except KeyError: + pass + else: + found_module_key = False + for theme_config in theme_configs: + try: + segment_data = theme_config['segment_data'] + except KeyError: + pass + else: + if module: + try: + yield segment_data[module + '.' + name][key] + found_module_key = True + except KeyError: + pass + if not found_module_key: + try: + yield segment_data[name][key] + except KeyError: + pass + yield default + + +def get_segment_key(merge, *args, **kwargs): + if merge: + ret = None + for value in list_segment_key_values(*args, **kwargs): + if ret is None: + ret = value + elif isinstance(ret, dict) and isinstance(value, dict): + old_ret = ret + ret = value.copy() + ret.update(old_ret) + else: + return ret + return ret + else: + return next(list_segment_key_values(*args, **kwargs)) def get_function(data, segment): @@ -33,7 +65,7 @@ def get_function(data, segment): def get_string(data, segment): - return data['get_key'](segment, None, 'contents'), None, None + return data['get_key'](False, segment, None, 'contents'), None, None def get_filler(data, segment): @@ -129,8 +161,8 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non 'path': common_config['paths'], } - def get_key(segment, module, key, default=None): - return get_segment_key(segment, theme_configs, key, module, default) + def get_key(merge, segment, module, key, default=None): + return get_segment_key(merge, segment, theme_configs, key, module, default) data['get_key'] = get_key def get(segment, side): @@ -146,7 +178,7 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non pl.exception('Failed to generate segment from {0!r}: {1}', segment, str(e), prefix='segment_generator') return None - if not get_key(segment, module, 'display', True): + if not get_key(False, segment, module, 'display', True): return None if segment_type == 'function': @@ -155,7 +187,7 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non highlight_group = segment.get('highlight_group') or segment.get('name') if segment_type in ('function', 'segment_list'): - args = dict(((str(k), v) for k, v in get_key(segment, module, 'args', {}).items())) + args = dict(((str(k), v) for k, v in get_key(True, segment, module, 'args', {}).items())) if segment_type == 'segment_list': # Handle startup and shutdown of _contents_func? @@ -219,8 +251,8 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non 'type': segment_type, 'highlight_group': highlight_group, 'divider_highlight_group': None, - 'before': get_key(segment, module, 'before', ''), - 'after': get_key(segment, module, 'after', ''), + 'before': get_key(False, segment, module, 'before', ''), + 'after': get_key(False, segment, module, 'after', ''), 'contents_func': contents_func, 'contents': contents, 'priority': segment.get('priority', None), From 416a0efd84e31a0d17cf60ba81515b123b406bd6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 00:00:15 +0400 Subject: [PATCH 06/16] Copy a number of unicode values to powerline.json --- powerline/config_files/themes/powerline.json | 64 ++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json index 40cd0d6e..0122a3d7 100644 --- a/powerline/config_files/themes/powerline.json +++ b/powerline/config_files/themes/powerline.json @@ -19,6 +19,33 @@ "contents": " " }, + "powerline.segments.common.cwd": { + "args": { + "ellipsis": "⋯" + } + }, + "powerline.segments.common.network_load": { + "args": { + "recv_format": "⬇ {value:>8}", + "sent_format": "⬆ {value:>8}" + } + }, + "powerline.segments.common.now_playing": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + "powerline.segments.common.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥" + } + }, "powerline.segments.common.uptime": { "before": "⇑ " }, @@ -35,6 +62,43 @@ "before": " " }, + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "R": "REPLACE", + "Rv": "V·RPLCE", + "c": "COMMND", + "cv": "VIM EX", + "ce": "EX", + "r": "PROMPT", + "rm": "MORE", + "r?": "CONFIRM", + "!": "SHELL" + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows} × {vcols}", + "v_text_oneline": "C:{vcols}", + "v_text_multiline": "L:{rows}", + "V_text": "L:{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "" + } + }, "powerline.segments.vim.modified_indicator": { "args": { "text": "+" From 0255df2f7b1df623f60d0c223fc7cea6eaedb46e Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 00:01:35 +0400 Subject: [PATCH 07/16] Allow checking NowPlayingSegment --- powerline/lint/inspect.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/powerline/lint/inspect.py b/powerline/lint/inspect.py index 345f1a5c..dc7b0182 100644 --- a/powerline/lint/inspect.py +++ b/powerline/lint/inspect.py @@ -44,12 +44,22 @@ def getconfigargspec(obj): else: obj = obj - argspec = getargspec(obj) + remove_self = False + try: + argspec = getargspec(obj) + except TypeError: # Workaround for now_playing segment + # TODO For NowPlayingSegment for linter: merge in information about + # player-specific arguments + argspec = getargspec(obj.__call__) + remove_self = True args = [] defaults = [] for i, arg in zip(count(1), reversed(argspec.args)): - if ((arg == 'segment_info' and getattr(obj, 'powerline_requires_segment_info', None)) or - arg == 'pl'): + if ( + (arg == 'segment_info' and getattr(obj, 'powerline_requires_segment_info', None)) + or arg == 'pl' + or (arg == 'self' and remove_self) + ): continue if argspec.defaults and len(argspec.defaults) >= i: default = argspec.defaults[-i] From 48254ea657fd3344e7d40e8af1ac1e99dc4d97e7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 01:33:37 +0400 Subject: [PATCH 08/16] Use more precise error messages in test_tabline.vim --- tests/test_tabline.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tabline.vim b/tests/test_tabline.vim index 7289c0ce..3819e74b 100755 --- a/tests/test_tabline.vim +++ b/tests/test_tabline.vim @@ -13,7 +13,7 @@ catch endtry if result isnot# '%#Pl_240_5789784_235_2500134_NONE# 1 %#Pl_240_5789784_235_2500134_NONE#./%#Pl_244_8421504_235_2500134_bold#abc %#Pl_244_8421504_235_2500134_NONE# %#Pl_240_5789784_235_2500134_NONE#2 %#Pl_240_5789784_235_2500134_NONE#./%#Pl_244_8421504_235_2500134_bold#def %#Pl_235_2500134_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                           %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs ' - call writefile(['Unexpected result', result], 'message.fail') + call writefile(['Unexpected tabline', result], 'message.fail') cquit endif @@ -27,7 +27,7 @@ catch endtry if result isnot# '%#Pl_240_5789784_235_2500134_NONE# ./%#Pl_244_8421504_235_2500134_bold#abc %#Pl_244_8421504_235_2500134_NONE# %#Pl_240_5789784_235_2500134_NONE#./%#Pl_244_8421504_235_2500134_bold#def %#Pl_235_2500134_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                               %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' - call writefile(['Unexpected result (2)', result], 'message.fail') + call writefile(['Unexpected tabline (2)', result], 'message.fail') cquit endif From fa5c1e8ce443ad1308917851266235334815acae Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 02:04:58 +0400 Subject: [PATCH 09/16] Add ASCII-only theme --- powerline/config_files/themes/ascii.json | 108 +++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 powerline/config_files/themes/ascii.json diff --git a/powerline/config_files/themes/ascii.json b/powerline/config_files/themes/ascii.json new file mode 100644 index 00000000..af0df089 --- /dev/null +++ b/powerline/config_files/themes/ascii.json @@ -0,0 +1,108 @@ +{ + "dividers": { + "left": { + "hard": " ", + "soft": "| " + }, + "right": { + "hard": " ", + "soft": " |" + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": "BR " + }, + + "line_current_symbol": { + "contents": "LN " + }, + + "powerline.segments.common.cwd": { + "args": { + "ellipsis": "..." + } + }, + "powerline.segments.common.network_load": { + "args": { + "recv_format": "DL {value:>8}", + "sent_format": "UL {value:>8}" + } + }, + "powerline.segments.common.now_playing": { + "args": { + "state_symbols": { + "fallback": "", + "play": ">", + "pause": "~", + "stop": "X" + } + } + }, + "powerline.segments.common.battery": { + "args": { + "full_heart": "O", + "empty_heart": "O" + } + }, + "powerline.segments.common.uptime": { + "before": "UP " + }, + "powerline.segments.common.date": { + "before": "" + }, + "powerline.segments.common.email_imap_alert": { + "before": "MAIL " + }, + "powerline.segments.common.virtualenv": { + "before": "(e) " + }, + "powerline.segments.common.hostname": { + "before": "H " + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N-OPER", + "v": "VISUAL", + "V": "V-LINE", + "^V": "V-BLCK", + "s": "SELECT", + "S": "S-LINE", + "^S": "S-BLCK", + "i": "INSERT", + "R": "REPLACE", + "Rv": "V-RPLCE", + "c": "COMMND", + "cv": "VIM EX", + "ce": "EX", + "r": "PROMPT", + "rm": "MORE", + "r?": "CONFIRM", + "!": "SHELL" + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows} x {vcols}", + "v_text_oneline": "C:{vcols}", + "v_text_multiline": "L:{rows}", + "V_text": "L:{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "RO" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + } + } +} From 625aa243d6e58f3051d899def2773e49cc55c036 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 02:10:29 +0400 Subject: [PATCH 10/16] Use ascii theme in local overrides --- tests/test_local_overrides.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_local_overrides.vim b/tests/test_local_overrides.vim index c054df78..5b80d26c 100755 --- a/tests/test_local_overrides.vim +++ b/tests/test_local_overrides.vim @@ -1,6 +1,6 @@ #!/usr/bin/vim -S let g:powerline_config_path = expand(':p:h:h') . '/powerline/config_files' -let g:powerline_config_overrides = {'common': {'dividers': {'left': {'hard': ' ', 'soft': ' > '}, 'right': {'hard': ' ', 'soft': ' < '}}}} +let g:powerline_config_overrides = {'common': {'default_top_theme': 'ascii'}} let g:powerline_theme_overrides__default = {'segment_data': {'line_current_symbol': {'contents': 'LN '}, 'branch': {'before': 'B '}}} try python import powerline.vim From 3c93ac2f4465aa010af9d2a8721a861f85856852 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 02:25:07 +0400 Subject: [PATCH 11/16] Add unicode-only theme --- powerline/config_files/themes/unicode.json | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 powerline/config_files/themes/unicode.json diff --git a/powerline/config_files/themes/unicode.json b/powerline/config_files/themes/unicode.json new file mode 100644 index 00000000..0179f984 --- /dev/null +++ b/powerline/config_files/themes/unicode.json @@ -0,0 +1,108 @@ +{ + "dividers": { + "left": { + "hard": "▌ ", + "soft": "│ " + }, + "right": { + "hard": " ▐", + "soft": " │" + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": "⎇ " + }, + + "line_current_symbol": { + "contents": "␤ " + }, + + "powerline.segments.common.cwd": { + "args": { + "ellipsis": "⋯" + } + }, + "powerline.segments.common.network_load": { + "args": { + "recv_format": "⬇ {value:>8}", + "sent_format": "⬆ {value:>8}" + } + }, + "powerline.segments.common.now_playing": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + "powerline.segments.common.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥" + } + }, + "powerline.segments.common.uptime": { + "before": "⇑ " + }, + "powerline.segments.common.date": { + "before": "⌚ " + }, + "powerline.segments.common.email_imap_alert": { + "before": "✉ " + }, + "powerline.segments.common.virtualenv": { + "before": "ⓔ " + }, + "powerline.segments.common.hostname": { + "before": "⌂ " + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "R": "REPLACE", + "Rv": "V·RPLCE", + "c": "COMMND", + "cv": "VIM EX", + "ce": "EX", + "r": "PROMPT", + "rm": "MORE", + "r?": "CONFIRM", + "!": "SHELL" + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows} × {vcols}", + "v_text_oneline": "C:{vcols}", + "v_text_multiline": "L:{rows}", + "V_text": "L:{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "⊗" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + } + } +} From cd576f0d4e40b3f1cd4975d9560d77942d56485d Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 02:47:33 +0400 Subject: [PATCH 12/16] Add unicode_terminus top-level theme This one contains only glyphs present in terminus font. --- .../config_files/themes/unicode_terminus.json | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 powerline/config_files/themes/unicode_terminus.json diff --git a/powerline/config_files/themes/unicode_terminus.json b/powerline/config_files/themes/unicode_terminus.json new file mode 100644 index 00000000..c1310fca --- /dev/null +++ b/powerline/config_files/themes/unicode_terminus.json @@ -0,0 +1,108 @@ +{ + "dividers": { + "left": { + "hard": "▌ ", + "soft": "│ " + }, + "right": { + "hard": " ▐", + "soft": " │" + } + }, + "spaces": 1, + "segment_data": { + "branch": { + "before": "BR " + }, + + "line_current_symbol": { + "contents": "␤ " + }, + + "powerline.segments.common.cwd": { + "args": { + "ellipsis": "…" + } + }, + "powerline.segments.common.network_load": { + "args": { + "recv_format": "⇓ {value:>8}", + "sent_format": "⇑ {value:>8}" + } + }, + "powerline.segments.common.now_playing": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + "powerline.segments.common.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥" + } + }, + "powerline.segments.common.uptime": { + "before": "↑ " + }, + "powerline.segments.common.date": { + "before": "" + }, + "powerline.segments.common.email_imap_alert": { + "before": "MAIL " + }, + "powerline.segments.common.virtualenv": { + "before": "(e) " + }, + "powerline.segments.common.hostname": { + "before": "⌂ " + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NORMAL", + "no": "N·OPER", + "v": "VISUAL", + "V": "V·LINE", + "^V": "V·BLCK", + "s": "SELECT", + "S": "S·LINE", + "^S": "S·BLCK", + "i": "INSERT", + "R": "REPLACE", + "Rv": "V·RPLCE", + "c": "COMMND", + "cv": "VIM EX", + "ce": "EX", + "r": "PROMPT", + "rm": "MORE", + "r?": "CONFIRM", + "!": "SHELL" + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows} × {vcols}", + "v_text_oneline": "C:{vcols}", + "v_text_multiline": "L:{rows}", + "V_text": "L:{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "RO" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + } + } +} From 2b5734c767d831ac3bfb8e8a58e26a5cb633d484 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 03:03:56 +0400 Subject: [PATCH 13/16] Add unicode_terminus_condensed theme --- .../themes/unicode_terminus_condensed.json | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 powerline/config_files/themes/unicode_terminus_condensed.json diff --git a/powerline/config_files/themes/unicode_terminus_condensed.json b/powerline/config_files/themes/unicode_terminus_condensed.json new file mode 100644 index 00000000..e56309ac --- /dev/null +++ b/powerline/config_files/themes/unicode_terminus_condensed.json @@ -0,0 +1,109 @@ +{ + "dividers": { + "left": { + "hard": "▌", + "soft": "│" + }, + "right": { + "hard": "▐", + "soft": "│" + } + }, + "spaces": 0, + "segment_data": { + "branch": { + "before": "B " + }, + + "line_current_symbol": { + "contents": "␤" + }, + + "powerline.segments.common.cwd": { + "args": { + "use_path_separator": true, + "ellipsis": "…" + } + }, + "powerline.segments.common.network_load": { + "args": { + "recv_format": "⇓{value:>8}", + "sent_format": "⇑{value:>8}" + } + }, + "powerline.segments.common.now_playing": { + "args": { + "state_symbols": { + "fallback": "♫", + "play": "▶", + "pause": "▮▮", + "stop": "■" + } + } + }, + "powerline.segments.common.battery": { + "args": { + "full_heart": "♥", + "empty_heart": "♥" + } + }, + "powerline.segments.common.uptime": { + "before": "↑" + }, + "powerline.segments.common.date": { + "before": "" + }, + "powerline.segments.common.email_imap_alert": { + "before": "M " + }, + "powerline.segments.common.virtualenv": { + "before": "E " + }, + "powerline.segments.common.hostname": { + "before": "⌂" + }, + + "powerline.segments.vim.mode": { + "args": { + "override": { + "n": "NML", + "no": "NOP", + "v": "VIS", + "V": "VLN", + "^V": "VBL", + "s": "SEL", + "S": "SLN", + "^S": "SBL", + "i": "INS", + "R": "REP", + "Rv": "VRP", + "c": "CMD", + "cv": "VEX", + "ce": " EX", + "r": "PRT", + "rm": "MOR", + "r?": "CON", + "!": " SH" + } + } + }, + "powerline.segments.vim.visual_range": { + "args": { + "CTRL_V_text": "{rows}×{vcols}", + "v_text_oneline": "C:{vcols}", + "v_text_multiline": "L:{rows}", + "V_text": "L:{rows}" + } + }, + "powerline.segments.vim.readonly_indicator": { + "args": { + "text": "RO" + } + }, + "powerline.segments.vim.modified_indicator": { + "args": { + "text": "+" + } + } + } +} From c3e6329262943c1e574a5bcd3f9b56b868efbb97 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 03:09:31 +0400 Subject: [PATCH 14/16] Add description of shipped themes to documentation --- docs/source/configuration/reference.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index 6722865f..dc14209c 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -226,7 +226,18 @@ override those from each previous file. It is required that file `{top_theme}` component of the file name is obtained either from :ref:`top_theme extension-specific key ` or from :ref:`default_top_theme -common configuration key `. +common configuration key `. Powerline ships +with the following top themes: + +========================== ==================================================== +Theme Description +========================== ==================================================== +powerline Default powerline theme with fancy powerline symbols +unicode Theme without any symbols from private use area +unicode_terminus Theme containing only symbols from terminus PCF font +unicode_terminus_condensed Like above, but occupies as less space as possible +ascii Theme without any unicode characters at all +========================== ==================================================== ``name`` Name of the theme. From d4735c87df9339392ec37870dd49496f35d8e1b5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 12:48:17 +0400 Subject: [PATCH 15/16] Fix labels in configuration/reference.rst --- docs/source/configuration/reference.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index dc14209c..f8e7f148 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -413,11 +413,12 @@ ascii Theme without any unicode characters at all *not* included in any modes, *except* for the modes in this list. ``display`` - .. _config-themes-seg-display: Boolean. If false disables displaying of the segment. Defaults to ``True``. ``segments`` + .. _config-themes-seg-segments: + A list of subsegments. From 1a87006310d342c807b79204c9f7484850755c8f Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 6 Aug 2014 12:52:52 +0400 Subject: [PATCH 16/16] Refactor powerline.lint.inspect: do not special-case *ThreadedSegment Fixes now_playing segment handling in python-3.4 --- powerline/lib/threaded.py | 34 ++++++++++- powerline/lint/inspect.py | 101 +++++++++++++++------------------ powerline/segment.py | 4 +- powerline/segments/__init__.py | 49 ++++++++++++++++ powerline/segments/common.py | 3 +- 5 files changed, 130 insertions(+), 61 deletions(-) diff --git a/powerline/lib/threaded.py b/powerline/lib/threaded.py index b253ae8f..acb258c3 100644 --- a/powerline/lib/threaded.py +++ b/powerline/lib/threaded.py @@ -2,9 +2,11 @@ from __future__ import absolute_import -from powerline.lib.monotonic import monotonic - from threading import Thread, Lock, Event +from types import MethodType + +from powerline.lib.monotonic import monotonic +from powerline.segments import Segment class MultiRunnedThread(object): @@ -28,12 +30,14 @@ class MultiRunnedThread(object): return None -class ThreadedSegment(MultiRunnedThread): +class ThreadedSegment(Segment, MultiRunnedThread): min_sleep_time = 0.1 update_first = True interval = 1 daemon = False + argmethods = ('render', 'set_state') + def __init__(self): super(ThreadedSegment, self).__init__() self.run_once = True @@ -145,10 +149,34 @@ class ThreadedSegment(MultiRunnedThread): def debug(self, *args, **kwargs): self.pl.debug(prefix=self.__class__.__name__, *args, **kwargs) + def argspecobjs(self): + for name in self.argmethods: + try: + yield name, getattr(self, name) + except AttributeError: + pass + + def additional_args(self): + return (('interval', self.interval),) + + def omitted_args(self, name, method): + if isinstance(getattr(self, name, None), MethodType): + omitted_indexes = (0,) + else: + omitted_indexes = () + if name.startswith('render'): + if omitted_indexes: + omitted_indexes += (1,) + else: + omitted_indexes = (0,) + return omitted_indexes + class KwThreadedSegment(ThreadedSegment): update_first = True + argmethods = ('render', 'set_state', 'key', 'render_one') + def __init__(self): super(KwThreadedSegment, self).__init__() self.updated = True diff --git a/powerline/lint/inspect.py b/powerline/lint/inspect.py index dc7b0182..58d0dc05 100644 --- a/powerline/lint/inspect.py +++ b/powerline/lint/inspect.py @@ -1,72 +1,63 @@ # vim:fileencoding=utf-8:noet from __future__ import absolute_import + from inspect import ArgSpec, getargspec -from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment from itertools import count +from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment +from powerline.segments import Segment + def getconfigargspec(obj): - if isinstance(obj, ThreadedSegment): - args = ['interval'] - defaults = [getattr(obj, 'interval', 1)] - if obj.update_first: - args.append('update_first') - defaults.append(True) - methods = ['render', 'set_state'] - if isinstance(obj, KwThreadedSegment): - methods += ['key', 'render_one'] - - for method in methods: - if hasattr(obj, method): - # Note: on = i: - default = argspec.defaults[-i] - defaults.append(default) - args.append(arg) - else: - args.insert(0, arg) - argspec = ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults)) + if hasattr(obj, 'powerline_origin'): + obj = obj.powerline_origin else: - if hasattr(obj, 'powerline_origin'): - obj = obj.powerline_origin - else: - obj = obj + obj = obj - remove_self = False - try: - argspec = getargspec(obj) - except TypeError: # Workaround for now_playing segment - # TODO For NowPlayingSegment for linter: merge in information about - # player-specific arguments - argspec = getargspec(obj.__call__) - remove_self = True - args = [] - defaults = [] - for i, arg in zip(count(1), reversed(argspec.args)): + args = [] + defaults = [] + + if isinstance(obj, Segment): + additional_args = obj.additional_args() + argspecobjs = obj.argspecobjs() + get_omitted_args = obj.omitted_args + else: + additional_args = () + argspecobjs = ((None, obj),) + get_omitted_args = lambda *args: () + + for arg in additional_args: + args.append(arg[0]) + if len(arg) > 1: + defaults.append(arg[1]) + + requires_segment_info = getattr(obj, 'powerline_requires_segment_info', False) + requires_filesystem_watcher = getattr(obj, 'powerline_requires_filesystem_watcher', False) + + for name, method in argspecobjs: + argspec = getargspec(method) + omitted_args = get_omitted_args(name, method) + largs = len(argspec.args) + for i, arg in enumerate(reversed(argspec.args)): if ( - (arg == 'segment_info' and getattr(obj, 'powerline_requires_segment_info', None)) + largs - (i + 1) in omitted_args or arg == 'pl' - or (arg == 'self' and remove_self) + or (arg == 'create_watcher' and requires_filesystem_watcher) + or (arg == 'segment_info' and requires_segment_info) ): continue - if argspec.defaults and len(argspec.defaults) >= i: - default = argspec.defaults[-i] + if argspec.defaults and len(argspec.defaults) > i: + if arg in args: + idx = args.index(arg) + if len(args) - idx > len(defaults): + args.pop(idx) + else: + continue + default = argspec.defaults[-(i + 1)] defaults.append(default) args.append(arg) else: - args.insert(0, arg) - argspec = ArgSpec(args=args, varargs=argspec.varargs, keywords=argspec.keywords, defaults=tuple(defaults)) + if arg not in args: + args.insert(0, arg) - return argspec + return ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults)) diff --git a/powerline/segment.py b/powerline/segment.py index 8d5a956c..97538813 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -1,10 +1,10 @@ # vim:fileencoding=utf-8:noet - from __future__ import absolute_import, unicode_literals, division, print_function -from powerline.lib.file_watcher import create_file_watcher import sys +from powerline.lib.file_watcher import create_file_watcher + def list_segment_key_values(segment, theme_configs, key, module=None, default=None): try: diff --git a/powerline/segments/__init__.py b/powerline/segments/__init__.py index 3ad9513f..3c2da39c 100644 --- a/powerline/segments/__init__.py +++ b/powerline/segments/__init__.py @@ -1,2 +1,51 @@ +# vim:fileencoding=utf-8:noet +from __future__ import absolute_import + +import sys + from pkgutil import extend_path +from types import MethodType + + __path__ = extend_path(__path__, __name__) + + +class Segment(object): + '''Base class for any segment that is not a function + + Required for powerline.lint.inspect to work properly. + ''' + if sys.version_info < (3, 4): + def argspecobjs(self): + yield '__call__', self.__call__ + else: + def argspecobjs(self): # NOQA + yield '__call__', self + + argspecobjs.__doc__ = ( + '''Return a list of valid arguments for inspect.getargspec + + Used to determine function arguments. + ''' + ) + + def omitted_args(self, name, method): + '''List arguments which should be omitted + + Returns a tuple with indexes of omitted arguments. + + .. note::``segment_info``, ``create_watcher`` and ``pl`` will be omitted + regardless of the below return (for ``segment_info`` and + ``create_watcher``: only if object was marked to require segment + info or filesystem watcher). + ''' + if isinstance(self.__call__, MethodType): + return (0,) + else: + return () + + @staticmethod + def additional_args(): + '''Returns a list of (additional argument name[, default value]) tuples. + ''' + return () diff --git a/powerline/segments/common.py b/powerline/segments/common.py index a0e54492..365a92da 100644 --- a/powerline/segments/common.py +++ b/powerline/segments/common.py @@ -19,6 +19,7 @@ from powerline.lib.monotonic import monotonic from powerline.lib.humanize_bytes import humanize_bytes from powerline.lib.unicode import u from powerline.theme import requires_segment_info, requires_filesystem_watcher +from powerline.segments import Segment from collections import namedtuple @@ -904,7 +905,7 @@ STATE_SYMBOLS = { } -class NowPlayingSegment(object): +class NowPlayingSegment(Segment): def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs): player_func = getattr(self, 'player_{0}'.format(player)) stats = {