Merge pull request #958 from ZyX-I/theme-hierarchy

Implement theme hierarchy
This commit is contained in:
Nikolai Aleksandrovich Pavlov 2014-08-06 13:04:50 +04:00
commit 22fdc10534
28 changed files with 1290 additions and 398 deletions

View File

@ -17,9 +17,11 @@ Powerline provides default configurations in the following locations:
:file:`powerline/config.json`
:ref:`Colorschemes <config-colors>`
:file:`powerline/colorschemes/{name}.json`,
:file:`powerline/colorscheme/__main__.json`,
:file:`powerline/colorscheme/{extension}/__main__.json`,
:file:`powerline/colorschemes/{extension}/{name}.json`
:ref:`Themes <config-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 <local-configuration-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

View File

@ -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,28 @@ 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 <config-ext-top_theme>` or from :ref:`default_top_theme
common configuration key <config-common-default_top_theme>`. 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.
@ -223,23 +247,45 @@ 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``
A dict where keys are segment names or strings ``{module}.{name}``. Used to
specify default values for various keys:
:ref:`after <config-themes-seg-after>`,
:ref:`args <config-themes-seg-args>` (only for function segments),
:ref:`before <config-themes-seg-before>`,
:ref:`contents <config-themes-seg-contents>` (only for string segments
if :ref:`name <config-themes-seg-name>` is defined),
:ref:`display <config-themes-seg-display>`.
Key :ref:`args <config-themes-seg-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 <config-ext-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
<config-ext-theme>`. For the :ref:`default theme <config-ext-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.
@ -259,8 +305,8 @@ Themes
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
@ -367,11 +413,12 @@ Themes
*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.

View File

@ -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 <config-themes>`
'''
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 <config-colorschemes>`.
'''
# 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 <config-themes>`
'''
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.

View File

@ -1,17 +1,6 @@
{
"common": {
"term_truecolor": false,
"dividers": {
"left": {
"hard": " ",
"soft": " "
},
"right": {
"hard": " ",
"soft": " "
}
},
"spaces": 1
"term_truecolor": false
},
"ext": {
"ipython": {

View File

@ -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": "+"
}
}
}
}

View File

@ -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": "+"
}
}
}
}

View File

@ -0,0 +1,14 @@
{
"segment_data": {
"hostname": {
"args": {
"only_if_ssh": true
}
},
"powerline.segments.common.cwd": {
"args": {
"dir_limit_depth": 3
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -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": "+"
}
}
}
}

View File

@ -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": "+"
}
}
}
}

View File

@ -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": "+"
}
}
}
}

View File

@ -0,0 +1,10 @@
{
"segment_data": {
"line_percent": {
"args": {
"gradient": true
},
"after": "%"
}
}
}

View File

@ -1,19 +1,4 @@
{
"segment_data": {
"branch": {
"before": " "
},
"modified_indicator": {
"args": { "text": "+" }
},
"line_percent": {
"args": { "gradient": true },
"after": "%"
},
"line_current_symbol": {
"contents": " "
}
},
"segments": {
"left": [
{

View File

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

View File

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

View File

@ -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,30 +1071,45 @@ 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:
for segments in theme.get('segments', {}).values():
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():
for segment in segments:
if 'name' in segment:
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
module = segment.get('module', theme.get('default_module', 'powerline.segments.' + ext))
if key == unicode(module) + '.' + unicode(segment['name']):
found = True
break
if found:
break
if found:
break
else:
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)
@ -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(),
segment_data=Spec().unknown_spec(
Spec().func(check_segment_data_key),
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_segments, *args, **kwargs)),
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),
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,12 +1374,17 @@ 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]
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:
@ -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

View File

@ -1,62 +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 <python-2.6 it may return simple tuple, not
# ArgSpec instance.
argspec = getargspec(getattr(obj, method))
for i, arg in zip(count(1), reversed(argspec.args)):
if (arg == 'self' or
(arg == 'segment_info' and
getattr(obj, 'powerline_requires_segment_info', None)) or
(arg == 'create_watcher' and
getattr(obj, 'powerline_requires_filesystem_watcher', None)) or
(arg == 'pl') or
(method.startswith('render') and (1 if argspec.args[0] == 'self' else 0) + i == len(argspec.args)) or
arg in args):
continue
if argspec.defaults and len(argspec.defaults) >= 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))
else:
if hasattr(obj, 'powerline_origin'):
obj = obj.powerline_origin
else:
obj = obj
argspec = getargspec(obj)
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 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 (
largs - (i + 1) in omitted_args
or arg == 'pl'
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:
if arg not in args:
args.insert(0, arg)
argspec = ArgSpec(args=args, varargs=argspec.varargs, keywords=argspec.keywords, defaults=tuple(defaults))
return argspec
return ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults))

View File

@ -1,25 +1,57 @@
# 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 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]
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]
yield segment[key]
except KeyError:
pass
return default
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),

View File

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

View File

@ -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
@ -896,7 +897,6 @@ Highlight groups used: ``email_alert_gradient`` (gradient), ``email_alert``.
''')
class NowPlayingSegment(object):
STATE_SYMBOLS = {
'fallback': '',
'play': '',
@ -904,21 +904,23 @@ class NowPlayingSegment(object):
'stop': '',
}
def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', **kwargs):
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 = {
'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 +967,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 +999,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 +1027,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 +1073,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],

View File

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

View File

@ -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,38 +177,28 @@ 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': {
'2/themes/powerline': {
'spaces': 1,
}
},
'3/config': {
'common': {
'3/themes/powerline': {
'dividers': {
'left': {
'hard': '>>',
}
}
}
},
})) as p:
self.assertRenderEqual(p, '{12} bt {2-}>>{--}')

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#!/usr/bin/vim -S
let g:powerline_config_path = expand('<sfile>: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

View File

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