summaryrefslogtreecommitdiffstats
path: root/powerline
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:40:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:40:16 +0000
commit3f25952c13d5847d510c0cae22a8ba876638d570 (patch)
tree02f505f016ed5a1029277dcae520d5e2a75906fb /powerline
parentInitial commit. (diff)
downloadpowerline-3f25952c13d5847d510c0cae22a8ba876638d570.tar.xz
powerline-3f25952c13d5847d510c0cae22a8ba876638d570.zip
Adding upstream version 2.8.3.upstream/2.8.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'powerline')
-rw-r--r--powerline/__init__.py991
-rw-r--r--powerline/bindings/__init__.py0
-rwxr-xr-xpowerline/bindings/awesome/powerline-awesome.py20
-rw-r--r--powerline/bindings/awesome/powerline.lua15
-rwxr-xr-xpowerline/bindings/bar/powerline-bar.py60
-rw-r--r--powerline/bindings/bash/powerline.sh153
-rw-r--r--powerline/bindings/config.py286
-rw-r--r--powerline/bindings/fish/powerline-setup.fish100
-rwxr-xr-xpowerline/bindings/i3/powerline-i3.py52
-rw-r--r--powerline/bindings/ipython/__init__.py0
-rw-r--r--powerline/bindings/ipython/post_0_11.py126
-rw-r--r--powerline/bindings/ipython/pre_0_11.py146
-rw-r--r--powerline/bindings/ipython/since_5.py81
-rw-r--r--powerline/bindings/ipython/since_7.py78
-rwxr-xr-xpowerline/bindings/lemonbar/powerline-lemonbar.py61
-rw-r--r--powerline/bindings/pdb/__init__.py183
-rwxr-xr-xpowerline/bindings/pdb/__main__.py9
-rw-r--r--powerline/bindings/qtile/__init__.py0
-rw-r--r--powerline/bindings/qtile/widget.py61
-rw-r--r--powerline/bindings/rc/powerline.rc92
-rw-r--r--powerline/bindings/shell/powerline.sh239
-rw-r--r--powerline/bindings/tcsh/powerline.tcsh60
-rw-r--r--powerline/bindings/tmux/__init__.py85
-rw-r--r--powerline/bindings/tmux/powerline-base.conf11
-rw-r--r--powerline/bindings/tmux/powerline.conf2
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.7_plus.conf3
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.8.conf5
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.8_minus.conf11
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.8_plus.conf5
-rw-r--r--powerline/bindings/tmux/powerline_tmux_1.9_plus.conf9
-rw-r--r--powerline/bindings/tmux/powerline_tmux_2.1_plus.conf3
-rw-r--r--powerline/bindings/vim/__init__.py482
-rw-r--r--powerline/bindings/vim/autoload/powerline/debug.vim20
-rw-r--r--powerline/bindings/vim/plugin/powerline.vim169
-rw-r--r--powerline/bindings/wm/__init__.py72
-rw-r--r--powerline/bindings/wm/awesome.py59
-rw-r--r--powerline/bindings/zsh/__init__.py225
-rw-r--r--powerline/bindings/zsh/powerline.zsh216
-rw-r--r--powerline/colorscheme.py147
-rw-r--r--powerline/commands/__init__.py0
-rw-r--r--powerline/commands/config.py109
-rw-r--r--powerline/commands/daemon.py24
-rw-r--r--powerline/commands/lemonbar.py35
-rwxr-xr-xpowerline/commands/lint.py21
-rw-r--r--powerline/commands/main.py190
-rw-r--r--powerline/config.py10
-rw-r--r--powerline/config_files/colors.json124
-rw-r--r--powerline/config_files/colorschemes/default.json57
-rw-r--r--powerline/config_files/colorschemes/ipython/__main__.json6
-rw-r--r--powerline/config_files/colorschemes/pdb/__main__.json8
-rw-r--r--powerline/config_files/colorschemes/pdb/default.json5
-rw-r--r--powerline/config_files/colorschemes/pdb/solarized.json5
-rw-r--r--powerline/config_files/colorschemes/shell/__main__.json10
-rw-r--r--powerline/config_files/colorschemes/shell/default.json16
-rw-r--r--powerline/config_files/colorschemes/shell/solarized.json13
-rw-r--r--powerline/config_files/colorschemes/solarized.json40
-rw-r--r--powerline/config_files/colorschemes/tmux/default.json14
-rw-r--r--powerline/config_files/colorschemes/tmux/solarized.json14
-rw-r--r--powerline/config_files/colorschemes/vim/__main__.json51
-rw-r--r--powerline/config_files/colorschemes/vim/default.json154
-rw-r--r--powerline/config_files/colorschemes/vim/solarized.json121
-rw-r--r--powerline/config_files/colorschemes/vim/solarizedlight.json122
-rw-r--r--powerline/config_files/config.json53
-rw-r--r--powerline/config_files/themes/ascii.json153
-rw-r--r--powerline/config_files/themes/ipython/in.json25
-rw-r--r--powerline/config_files/themes/ipython/in2.json12
-rw-r--r--powerline/config_files/themes/ipython/out.json24
-rw-r--r--powerline/config_files/themes/ipython/rewrite.json23
-rw-r--r--powerline/config_files/themes/pdb/default.json27
-rw-r--r--powerline/config_files/themes/powerline.json151
-rw-r--r--powerline/config_files/themes/powerline_terminus.json151
-rw-r--r--powerline/config_files/themes/powerline_unicode7.json165
-rw-r--r--powerline/config_files/themes/shell/__main__.json14
-rw-r--r--powerline/config_files/themes/shell/continuation.json12
-rw-r--r--powerline/config_files/themes/shell/default.json43
-rw-r--r--powerline/config_files/themes/shell/default_leftonly.json34
-rw-r--r--powerline/config_files/themes/shell/select.json13
-rw-r--r--powerline/config_files/themes/tmux/default.json28
-rw-r--r--powerline/config_files/themes/unicode.json151
-rw-r--r--powerline/config_files/themes/unicode_terminus.json151
-rw-r--r--powerline/config_files/themes/unicode_terminus_condensed.json151
-rw-r--r--powerline/config_files/themes/vim/__main__.json10
-rw-r--r--powerline/config_files/themes/vim/cmdwin.json18
-rw-r--r--powerline/config_files/themes/vim/default.json128
-rw-r--r--powerline/config_files/themes/vim/help.json36
-rw-r--r--powerline/config_files/themes/vim/plugin_commandt.json26
-rw-r--r--powerline/config_files/themes/vim/plugin_gundo-preview.json18
-rw-r--r--powerline/config_files/themes/vim/plugin_gundo.json18
-rw-r--r--powerline/config_files/themes/vim/plugin_nerdtree.json17
-rw-r--r--powerline/config_files/themes/vim/quickfix.json40
-rw-r--r--powerline/config_files/themes/vim/tabline.json93
-rw-r--r--powerline/config_files/themes/wm/default.json17
-rw-r--r--powerline/dist/systemd/powerline-daemon.service10
-rw-r--r--powerline/ipython.py68
-rw-r--r--powerline/lemonbar.py21
-rw-r--r--powerline/lib/__init__.py28
-rw-r--r--powerline/lib/config.py218
-rwxr-xr-xpowerline/lib/debug.py97
-rw-r--r--powerline/lib/dict.py88
-rw-r--r--powerline/lib/encoding.py125
-rw-r--r--powerline/lib/humanize_bytes.py25
-rw-r--r--powerline/lib/inotify.py184
-rw-r--r--powerline/lib/memoize.py42
-rw-r--r--powerline/lib/monotonic.py100
-rw-r--r--powerline/lib/overrides.py80
-rw-r--r--powerline/lib/path.py18
-rw-r--r--powerline/lib/shell.py133
-rw-r--r--powerline/lib/threaded.py262
-rw-r--r--powerline/lib/unicode.py283
-rw-r--r--powerline/lib/url.py17
-rw-r--r--powerline/lib/vcs/__init__.py276
-rw-r--r--powerline/lib/vcs/bzr.py108
-rw-r--r--powerline/lib/vcs/git.py208
-rw-r--r--powerline/lib/vcs/mercurial.py88
-rw-r--r--powerline/lib/watcher/__init__.py76
-rw-r--r--powerline/lib/watcher/inotify.py268
-rw-r--r--powerline/lib/watcher/stat.py44
-rw-r--r--powerline/lib/watcher/tree.py90
-rw-r--r--powerline/lib/watcher/uv.py207
-rw-r--r--powerline/lint/__init__.py625
-rw-r--r--powerline/lint/checks.py866
-rw-r--r--powerline/lint/context.py68
-rw-r--r--powerline/lint/imp.py56
-rw-r--r--powerline/lint/inspect.py63
-rw-r--r--powerline/lint/markedjson/__init__.py19
-rw-r--r--powerline/lint/markedjson/composer.py119
-rw-r--r--powerline/lint/markedjson/constructor.py285
-rw-r--r--powerline/lint/markedjson/error.py241
-rw-r--r--powerline/lint/markedjson/events.py97
-rw-r--r--powerline/lint/markedjson/loader.py25
-rw-r--r--powerline/lint/markedjson/markedvalue.py151
-rw-r--r--powerline/lint/markedjson/nodes.py55
-rw-r--r--powerline/lint/markedjson/parser.py255
-rw-r--r--powerline/lint/markedjson/reader.py141
-rw-r--r--powerline/lint/markedjson/resolver.py131
-rw-r--r--powerline/lint/markedjson/scanner.py499
-rw-r--r--powerline/lint/markedjson/tokens.py72
-rw-r--r--powerline/lint/selfcheck.py16
-rw-r--r--powerline/lint/spec.py759
-rw-r--r--powerline/listers/__init__.py0
-rw-r--r--powerline/listers/i3wm.py63
-rw-r--r--powerline/listers/pdb.py37
-rw-r--r--powerline/listers/vim.py123
-rw-r--r--powerline/matchers/__init__.py6
-rw-r--r--powerline/matchers/vim/__init__.py19
-rw-r--r--powerline/matchers/vim/plugin/__init__.py6
-rw-r--r--powerline/matchers/vim/plugin/commandt.py14
-rw-r--r--powerline/matchers/vim/plugin/gundo.py16
-rw-r--r--powerline/matchers/vim/plugin/nerdtree.py15
-rw-r--r--powerline/pdb.py48
-rw-r--r--powerline/renderer.py606
-rw-r--r--powerline/renderers/__init__.py0
-rw-r--r--powerline/renderers/i3bar.py36
-rw-r--r--powerline/renderers/ipython/__init__.py34
-rw-r--r--powerline/renderers/ipython/pre_5.py56
-rw-r--r--powerline/renderers/ipython/since_5.py130
-rw-r--r--powerline/renderers/ipython/since_7.py91
-rw-r--r--powerline/renderers/lemonbar.py61
-rw-r--r--powerline/renderers/pango_markup.py39
-rw-r--r--powerline/renderers/pdb.py50
-rw-r--r--powerline/renderers/shell/__init__.py182
-rw-r--r--powerline/renderers/shell/bash.py96
-rw-r--r--powerline/renderers/shell/ksh.py19
-rw-r--r--powerline/renderers/shell/rcsh.py7
-rw-r--r--powerline/renderers/shell/readline.py14
-rw-r--r--powerline/renderers/shell/tcsh.py31
-rw-r--r--powerline/renderers/shell/zsh.py16
-rw-r--r--powerline/renderers/tmux.py81
-rw-r--r--powerline/renderers/vim.py188
-rw-r--r--powerline/segment.py450
-rw-r--r--powerline/segments/__init__.py63
-rw-r--r--powerline/segments/common/__init__.py0
-rw-r--r--powerline/segments/common/bat.py302
-rw-r--r--powerline/segments/common/env.py201
-rw-r--r--powerline/segments/common/mail.py78
-rw-r--r--powerline/segments/common/net.py315
-rw-r--r--powerline/segments/common/players.py636
-rw-r--r--powerline/segments/common/sys.py184
-rw-r--r--powerline/segments/common/time.py123
-rw-r--r--powerline/segments/common/vcs.py89
-rw-r--r--powerline/segments/common/wthr.py234
-rw-r--r--powerline/segments/i3wm.py309
-rw-r--r--powerline/segments/ipython.py9
-rw-r--r--powerline/segments/pdb.py61
-rw-r--r--powerline/segments/shell.py196
-rw-r--r--powerline/segments/tmux.py22
-rw-r--r--powerline/segments/vim/__init__.py805
-rw-r--r--powerline/segments/vim/plugin/__init__.py6
-rw-r--r--powerline/segments/vim/plugin/ale.py52
-rw-r--r--powerline/segments/vim/plugin/capslock.py30
-rw-r--r--powerline/segments/vim/plugin/coc.py51
-rw-r--r--powerline/segments/vim/plugin/commandt.py97
-rw-r--r--powerline/segments/vim/plugin/nerdtree.py25
-rw-r--r--powerline/segments/vim/plugin/syntastic.py43
-rw-r--r--powerline/segments/vim/plugin/tagbar.py51
-rw-r--r--powerline/selectors/__init__.py0
-rw-r--r--powerline/selectors/vim.py10
-rw-r--r--powerline/shell.py38
-rw-r--r--powerline/theme.py182
-rw-r--r--powerline/version.py7
-rw-r--r--powerline/vim.py359
201 files changed, 22107 insertions, 0 deletions
diff --git a/powerline/__init__.py b/powerline/__init__.py
new file mode 100644
index 0000000..a50b939
--- /dev/null
+++ b/powerline/__init__.py
@@ -0,0 +1,991 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import sys
+import logging
+
+from threading import Lock, Event
+
+from powerline.colorscheme import Colorscheme
+from powerline.lib.config import ConfigLoader
+from powerline.lib.unicode import unicode, safe_unicode, FailedUnicode
+from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR
+from powerline.lib.dict import mergedicts
+from powerline.lib.encoding import get_preferred_output_encoding
+from powerline.lib.path import join
+from powerline.version import __version__
+
+class NotInterceptedError(BaseException):
+ pass
+
+
+def _config_loader_condition(path):
+ if path and os.path.isfile(path):
+ return path
+ return None
+
+
+def _find_config_files(search_paths, config_file, config_loader=None, loader_callback=None):
+ config_file += '.json'
+ found = False
+ for path in search_paths:
+ config_file_path = join(path, config_file)
+ if os.path.isfile(config_file_path):
+ yield config_file_path
+ found = True
+ elif config_loader:
+ config_loader.register_missing(_config_loader_condition, loader_callback, config_file_path)
+ if not found:
+ raise IOError('Config file not found in search paths ({0}): {1}'.format(
+ ', '.join(search_paths),
+ config_file
+ ))
+
+
+class PowerlineLogger(object):
+ '''Proxy class for logging.Logger instance
+
+ It emits messages in format ``{ext}:{prefix}:{message}`` where
+
+ ``{ext}``
+ is a used powerline extension (e.g. “vim”, “shell”, “ipython”).
+ ``{prefix}``
+ is a local prefix, usually a segment name.
+ ``{message}``
+ is the original message passed to one of the logging methods.
+
+ Each of the methods (``critical``, ``exception``, ``info``, ``error``,
+ ``warn``, ``debug``) expects to receive message in an ``str.format`` format,
+ not in printf-like format.
+
+ Log is saved to the location :ref:`specified by user <config-common-log>`.
+ '''
+
+ def __init__(self, use_daemon_threads, logger, ext):
+ self.logger = logger
+ self.ext = ext
+ self.use_daemon_threads = use_daemon_threads
+ self.prefix = ''
+ self.last_msgs = {}
+
+ def _log(self, attr, msg, *args, **kwargs):
+ prefix = kwargs.get('prefix') or self.prefix
+ prefix = self.ext + ((':' + prefix) if prefix else '')
+ msg = safe_unicode(msg)
+ if args or kwargs:
+ args = [safe_unicode(s) if isinstance(s, bytes) else s for s in args]
+ kwargs = dict((
+ (k, safe_unicode(v) if isinstance(v, bytes) else v)
+ for k, v in kwargs.items()
+ ))
+ msg = msg.format(*args, **kwargs)
+ msg = prefix + ':' + msg
+ key = attr + ':' + prefix
+ if msg != self.last_msgs.get(key):
+ getattr(self.logger, attr)(msg)
+ self.last_msgs[key] = msg
+
+ def critical(self, msg, *args, **kwargs):
+ self._log('critical', msg, *args, **kwargs)
+
+ def exception(self, msg, *args, **kwargs):
+ self._log('exception', msg, *args, **kwargs)
+
+ def info(self, msg, *args, **kwargs):
+ self._log('info', msg, *args, **kwargs)
+
+ def error(self, msg, *args, **kwargs):
+ self._log('error', msg, *args, **kwargs)
+
+ def warn(self, msg, *args, **kwargs):
+ self._log('warning', msg, *args, **kwargs)
+
+ def debug(self, msg, *args, **kwargs):
+ self._log('debug', msg, *args, **kwargs)
+
+
+_fallback_logger = None
+
+
+def get_fallback_logger(stream=None):
+ global _fallback_logger
+ if _fallback_logger:
+ return _fallback_logger
+
+ log_format = '%(asctime)s:%(levelname)s:%(message)s'
+ formatter = logging.Formatter(log_format)
+
+ level = logging.WARNING
+ handler = logging.StreamHandler(stream)
+ handler.setLevel(level)
+ handler.setFormatter(formatter)
+
+ logger = logging.Logger('powerline')
+ logger.setLevel(level)
+ logger.addHandler(handler)
+ _fallback_logger = PowerlineLogger(None, logger, '_fallback_')
+ return _fallback_logger
+
+
+def _generate_change_callback(lock, key, dictionary):
+ def on_file_change(path):
+ with lock:
+ dictionary[key] = True
+ return on_file_change
+
+
+def get_config_paths():
+ '''Get configuration paths from environment variables.
+
+ Uses $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS according to the XDG specification.
+
+ :return: list of paths
+ '''
+ config_home = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
+ config_path = join(config_home, 'powerline')
+ config_paths = [config_path]
+ config_dirs = os.environ.get('XDG_CONFIG_DIRS', DEFAULT_SYSTEM_CONFIG_DIR)
+ if config_dirs is not None:
+ config_paths[:0] = reversed([join(d, 'powerline') for d in config_dirs.split(':')])
+ plugin_path = join(os.path.realpath(os.path.dirname(__file__)), 'config_files')
+ config_paths.insert(0, plugin_path)
+ return config_paths
+
+
+def generate_config_finder(get_config_paths=get_config_paths):
+ '''Generate find_config_files function
+
+ This function will find .json file given its path.
+
+ :param function get_config_paths:
+ Function that being called with no arguments will return a list of paths
+ that should be searched for configuration files.
+
+ :return:
+ Function that being given configuration file name will return full path
+ to it or raise IOError if it failed to find the file.
+ '''
+ config_paths = get_config_paths()
+ return lambda *args: _find_config_files(config_paths, *args)
+
+
+def load_config(cfg_path, find_config_files, config_loader, loader_callback=None):
+ '''Load configuration file and setup watches
+
+ Watches are only set up if loader_callback is not None.
+
+ :param str cfg_path:
+ Path for configuration file that should be loaded.
+ :param function find_config_files:
+ Function that finds configuration file. Check out the description of
+ the return value of ``generate_config_finder`` function.
+ :param ConfigLoader config_loader:
+ Configuration file loader class instance.
+ :param function loader_callback:
+ Function that will be called by config_loader when change to
+ configuration file is detected.
+
+ :return: Configuration file contents.
+ '''
+ found_files = find_config_files(cfg_path, config_loader, loader_callback)
+ ret = None
+ for path in found_files:
+ if loader_callback:
+ config_loader.register(loader_callback, path)
+ if ret is None:
+ ret = config_loader.load(path)
+ else:
+ mergedicts(ret, config_loader.load(path))
+ return ret
+
+
+def _set_log_handlers(common_config, logger, get_module_attr, stream=None):
+ '''Set log handlers
+
+ :param dict common_config:
+ Configuration dictionary used to create handler.
+ :param logging.Logger logger:
+ Logger to which handlers will be attached.
+ :param func get_module_attr:
+ :py:func:`gen_module_attr_getter` output.
+ :param file stream:
+ Stream to use by default for :py:class:`logging.StreamHandler` in place
+ of :py:attr:`sys.stderr`. May be ``None``.
+ '''
+ log_targets = common_config['log_file']
+ num_handlers = 0
+ for log_target in log_targets:
+ if log_target is None:
+ log_target = ['logging.StreamHandler', []]
+ elif isinstance(log_target, unicode):
+ log_target = os.path.expanduser(log_target)
+ log_dir = os.path.dirname(log_target)
+ if log_dir and not os.path.isdir(log_dir):
+ os.mkdir(log_dir)
+ log_target = ['logging.FileHandler', [[log_target]]]
+ module, handler_class_name = log_target[0].rpartition('.')[::2]
+ module = module or 'logging.handlers'
+ try:
+ handler_class_args = log_target[1][0]
+ except IndexError:
+ if module == 'logging' and handler_class_name == 'StreamHandler':
+ handler_class_args = [stream]
+ else:
+ handler_class_args = ()
+ try:
+ handler_class_kwargs = log_target[1][1]
+ except IndexError:
+ handler_class_kwargs = {}
+ module = str(module)
+ handler_class_name = str(handler_class_name)
+ handler_class = get_module_attr(module, handler_class_name)
+ if not handler_class:
+ continue
+ handler = handler_class(*handler_class_args, **handler_class_kwargs)
+ try:
+ handler_level_name = log_target[2]
+ except IndexError:
+ handler_level_name = common_config['log_level']
+ try:
+ handler_format = log_target[3]
+ except IndexError:
+ handler_format = common_config['log_format']
+ handler.setLevel(getattr(logging, handler_level_name))
+ handler.setFormatter(logging.Formatter(handler_format))
+ logger.addHandler(handler)
+ num_handlers += 1
+ if num_handlers == 0 and log_targets:
+ raise ValueError('Failed to set up any handlers')
+
+
+def create_logger(common_config, use_daemon_threads=True, ext='__unknown__',
+ import_paths=None, imported_modules=None, stream=None):
+ '''Create logger according to provided configuration
+
+ :param dict common_config:
+ Common configuration, from :py:func:`finish_common_config`.
+ :param bool use_daemon_threads:
+ Whether daemon threads should be used. Argument to
+ :py:class:`PowerlineLogger` constructor.
+ :param str ext:
+ Used extension. Argument to :py:class:`PowerlineLogger` constructor.
+ :param set imported_modules:
+ Set where imported modules are saved. Argument to
+ :py:func:`gen_module_attr_getter`. May be ``None``, in this case new
+ empty set is used.
+ :param file stream:
+ Stream to use by default for :py:class:`logging.StreamHandler` in place
+ of :py:attr:`sys.stderr`. May be ``None``.
+
+ :return: Three objects:
+
+ #. :py:class:`logging.Logger` instance.
+ #. :py:class:`PowerlineLogger` instance.
+ #. Function, output of :py:func:`gen_module_attr_getter`.
+ '''
+ logger = logging.Logger('powerline')
+ level = getattr(logging, common_config['log_level'])
+ logger.setLevel(level)
+
+ pl = PowerlineLogger(use_daemon_threads, logger, ext)
+ get_module_attr = gen_module_attr_getter(
+ pl, common_config['paths'],
+ set() if imported_modules is None else imported_modules)
+
+ _set_log_handlers(common_config, logger, get_module_attr, stream)
+
+ return logger, pl, get_module_attr
+
+
+def get_default_theme(is_unicode=True):
+ '''Get default theme used by powerline
+
+ :param bool is_unicode:
+ If true, return theme for unicode environments, otherwise return theme
+ that is supposed to be ASCII-only.
+
+ :return: theme name.
+ '''
+ return 'powerline_terminus' if is_unicode else 'ascii'
+
+
+def finish_common_config(encoding, common_config):
+ '''Add default values to common config and expand ~ in paths
+
+ :param dict common_config:
+ Common configuration, as it was just loaded.
+
+ :return:
+ Copy of common configuration with all configuration keys and expanded
+ paths.
+ '''
+ encoding = encoding.lower()
+ default_top_theme = get_default_theme(
+ encoding.startswith('utf') or encoding.startswith('ucs'))
+
+ common_config = common_config.copy()
+ common_config.setdefault('default_top_theme', default_top_theme)
+ common_config.setdefault('paths', [])
+ common_config.setdefault('watcher', 'auto')
+ common_config.setdefault('log_level', 'WARNING')
+ common_config.setdefault('log_format', '%(asctime)s:%(levelname)s:%(message)s')
+ common_config.setdefault('term_truecolor', False)
+ common_config.setdefault('term_escape_style', 'auto')
+ common_config.setdefault('ambiwidth', 1)
+ common_config.setdefault('additional_escapes', None)
+ common_config.setdefault('reload_config', True)
+ common_config.setdefault('interval', None)
+ common_config.setdefault('log_file', [None])
+
+ if not isinstance(common_config['log_file'], list):
+ common_config['log_file'] = [common_config['log_file']]
+
+ common_config['paths'] = [
+ os.path.expanduser(path) for path in common_config['paths']
+ ]
+
+ return common_config
+
+
+if sys.version_info < (3,):
+ # `raise exception[0], None, exception[1]` is a SyntaxError in python-3*
+ # Not using ('''…''') because this syntax does not work in python-2.6
+ exec((
+ 'def reraise(exception):\n'
+ ' if type(exception) is tuple:\n'
+ ' raise exception[0], None, exception[1]\n'
+ ' else:\n'
+ ' raise exception\n'
+ ))
+else:
+ def reraise(exception):
+ if type(exception) is tuple:
+ raise exception[0].with_traceback(exception[1])
+ else:
+ raise exception
+
+
+def gen_module_attr_getter(pl, import_paths, imported_modules):
+ def get_module_attr(module, attr, prefix='powerline'):
+ '''Import module and get its attribute.
+
+ Replaces ``from {module} import {attr}``.
+
+ :param str module:
+ Module name, will be passed as first argument to ``__import__``.
+ :param str attr:
+ Module attribute, will be passed to ``__import__`` as the only value
+ in ``fromlist`` tuple.
+
+ :return:
+ Attribute value or ``None``. Note: there is no way to distinguish
+ between successful import of attribute equal to ``None`` and
+ unsuccessful import.
+ '''
+ oldpath = sys.path
+ sys.path = import_paths + sys.path
+ module = str(module)
+ attr = str(attr)
+ try:
+ imported_modules.add(module)
+ return getattr(__import__(module, fromlist=(attr,)), attr)
+ except Exception as e:
+ pl.exception('Failed to import attr {0} from module {1}: {2}', attr, module, str(e), prefix=prefix)
+ return None
+ finally:
+ sys.path = oldpath
+
+ return get_module_attr
+
+
+LOG_KEYS = set(('log_format', 'log_level', 'log_file', 'paths'))
+'''List of keys related to logging
+'''
+
+
+def _get_log_keys(common_config):
+ '''Return a common configuration copy with only log-related config left
+
+ :param dict common_config:
+ Common configuration.
+
+ :return:
+ :py:class:`dict` instance which has only keys from
+ :py:attr:`powerline.LOG_KEYS` left.
+ '''
+ return dict((
+ (k, v) for k, v in common_config.items() if k in LOG_KEYS
+ ))
+
+
+DEFAULT_UPDATE_INTERVAL = 2
+'''Default value for :ref:`update_interval <config-ext-update_interval>`
+'''
+
+
+class Powerline(object):
+ '''Main powerline class, entrance point for all powerline uses. Sets
+ powerline up and loads the configuration.
+
+ :param str ext:
+ extension used. Determines where configuration files will
+ searched and what renderer module will be used. Affected: used ``ext``
+ dictionary from :file:`powerline/config.json`, location of themes and
+ colorschemes, render module (``powerline.renders.{ext}``).
+ :param str renderer_module:
+ Overrides renderer module (defaults to ``ext``). Should be the name of
+ the package imported like this: ``powerline.renderers.{render_module}``.
+ If this parameter contains a dot ``powerline.renderers.`` is not
+ prepended. There is also a special case for renderers defined in
+ toplevel modules: ``foo.`` (note: dot at the end) tries to get renderer
+ from module ``foo`` (because ``foo`` (without dot) tries to get renderer
+ from module ``powerline.renderers.foo``). When ``.foo`` (with leading
+ dot) variant is used ``renderer_module`` will be
+ ``powerline.renderers.{ext}{renderer_module}``.
+ :param bool run_once:
+ Determines whether :py:meth:`render` method will be run only once
+ during python session.
+ :param Logger logger:
+ If present no new logger will be created and the provided logger will be
+ used.
+ :param bool use_daemon_threads:
+ When creating threads make them daemon ones.
+ :param Event shutdown_event:
+ Use this Event as shutdown_event instead of creating new event.
+ :param ConfigLoader config_loader:
+ Instance of the class that manages (re)loading of the configuration.
+ '''
+
+ def __init__(self, *args, **kwargs):
+ self.init_args = (args, kwargs)
+ self.init(*args, **kwargs)
+
+ def init(self,
+ ext,
+ renderer_module=None,
+ run_once=False,
+ logger=None,
+ use_daemon_threads=True,
+ shutdown_event=None,
+ config_loader=None):
+ '''Do actual initialization.
+
+ __init__ function only stores the arguments and runs this function. This
+ function exists for powerline to be able to reload itself: it is easier
+ to make ``__init__`` store arguments and call overridable ``init`` than
+ tell developers that each time they override Powerline.__init__ in
+ subclasses they must store actual arguments.
+ '''
+ self.ext = ext
+ self.run_once = run_once
+ self.logger = logger
+ self.had_logger = bool(self.logger)
+ self.use_daemon_threads = use_daemon_threads
+
+ if not renderer_module:
+ self.renderer_module = 'powerline.renderers.' + ext
+ elif '.' not in renderer_module:
+ self.renderer_module = 'powerline.renderers.' + renderer_module
+ elif renderer_module.startswith('.'):
+ self.renderer_module = 'powerline.renderers.' + ext + renderer_module
+ elif renderer_module.endswith('.'):
+ self.renderer_module = renderer_module[:-1]
+ else:
+ self.renderer_module = renderer_module
+
+ self.find_config_files = generate_config_finder(self.get_config_paths)
+
+ self.cr_kwargs_lock = Lock()
+ self.cr_kwargs = {}
+ self.cr_callbacks = {}
+ for key in ('main', 'colors', 'colorscheme', 'theme'):
+ self.cr_kwargs['load_' + key] = True
+ self.cr_callbacks[key] = _generate_change_callback(
+ self.cr_kwargs_lock,
+ 'load_' + key,
+ self.cr_kwargs
+ )
+
+ self.shutdown_event = shutdown_event or Event()
+ self.config_loader = config_loader or ConfigLoader(shutdown_event=self.shutdown_event, run_once=run_once)
+ self.run_loader_update = False
+
+ self.renderer_options = {}
+
+ self.prev_common_config = None
+ self.prev_ext_config = None
+ self.pl = None
+ self.setup_args = ()
+ self.setup_kwargs = {}
+ self.imported_modules = set()
+ self.update_interval = DEFAULT_UPDATE_INTERVAL
+
+ get_encoding = staticmethod(get_preferred_output_encoding)
+ '''Get encoding used by the current application
+
+ Usually returns encoding of the current locale.
+ '''
+
+ def create_logger(self):
+ '''Create logger
+
+ This function is used to create logger unless it was already specified
+ at initialization.
+
+ :return: Three objects:
+
+ #. :py:class:`logging.Logger` instance.
+ #. :py:class:`PowerlineLogger` instance.
+ #. Function, output of :py:func:`gen_module_attr_getter`.
+ '''
+ return create_logger(
+ common_config=self.common_config,
+ use_daemon_threads=self.use_daemon_threads,
+ ext=self.ext,
+ imported_modules=self.imported_modules,
+ stream=self.default_log_stream,
+ )
+
+ def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False):
+ '''(Re)create renderer object. Can be used after Powerline object was
+ successfully initialized. If any of the below parameters except
+ ``load_main`` is True renderer object will be recreated.
+
+ :param bool load_main:
+ Determines whether main configuration file (:file:`config.json`)
+ should be loaded. If appropriate configuration changes implies
+ ``load_colorscheme`` and ``load_theme`` and recreation of renderer
+ object. Won’t trigger recreation if only unrelated configuration
+ changed.
+ :param bool load_colors:
+ Determines whether colors configuration from :file:`colors.json`
+ should be (re)loaded.
+ :param bool load_colorscheme:
+ Determines whether colorscheme configuration should be (re)loaded.
+ :param bool load_theme:
+ Determines whether theme configuration should be reloaded.
+ '''
+ common_config_differs = False
+ ext_config_differs = False
+ if load_main:
+ self._purge_configs('main')
+ config = self.load_main_config()
+ self.common_config = finish_common_config(self.get_encoding(), config['common'])
+ if self.common_config != self.prev_common_config:
+ common_config_differs = True
+
+ load_theme = (load_theme
+ or not self.prev_common_config
+ or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme'])
+
+ log_keys_differ = (not self.prev_common_config or (
+ _get_log_keys(self.prev_common_config) != _get_log_keys(self.common_config)
+ ))
+
+ self.prev_common_config = self.common_config
+
+ if log_keys_differ:
+ if self.had_logger:
+ self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext)
+ self.get_module_attr = gen_module_attr_getter(
+ self.pl, self.common_config['paths'], self.imported_modules)
+ else:
+ self.logger, self.pl, self.get_module_attr = self.create_logger()
+ self.config_loader.pl = self.pl
+
+ if not self.run_once:
+ self.config_loader.set_watcher(self.common_config['watcher'])
+
+ mergedicts(self.renderer_options, dict(
+ pl=self.pl,
+ term_truecolor=self.common_config['term_truecolor'],
+ term_escape_style=self.common_config['term_escape_style'],
+ ambiwidth=self.common_config['ambiwidth'],
+ tmux_escape=self.common_config['additional_escapes'] == 'tmux',
+ screen_escape=self.common_config['additional_escapes'] == 'screen',
+ theme_kwargs={
+ 'ext': self.ext,
+ 'common_config': self.common_config,
+ 'run_once': self.run_once,
+ 'shutdown_event': self.shutdown_event,
+ 'get_module_attr': self.get_module_attr,
+ },
+ ))
+
+ if not self.run_once and self.common_config['reload_config']:
+ interval = self.common_config['interval']
+ self.config_loader.set_interval(interval)
+ self.run_loader_update = (interval is None)
+ if interval is not None and not self.config_loader.is_alive():
+ self.config_loader.start()
+
+ self.ext_config = config['ext'][self.ext]
+
+ top_theme = (
+ self.ext_config.get('top_theme')
+ or self.common_config['default_top_theme']
+ )
+ self.theme_levels = (
+ os.path.join('themes', top_theme),
+ os.path.join('themes', self.ext, '__main__'),
+ )
+ self.renderer_options['theme_kwargs']['top_theme'] = top_theme
+
+ if self.ext_config != self.prev_ext_config:
+ ext_config_differs = True
+ if (
+ not self.prev_ext_config
+ or self.ext_config.get('components') != self.prev_ext_config.get('components')
+ ):
+ self.setup_components(self.ext_config.get('components'))
+ if (
+ not self.prev_ext_config
+ or self.ext_config.get('local_themes') != self.prev_ext_config.get('local_themes')
+ ):
+ self.renderer_options['local_themes'] = self.get_local_themes(self.ext_config.get('local_themes'))
+ self.update_interval = self.ext_config.get('update_interval', 2)
+ load_colorscheme = (
+ load_colorscheme
+ or not self.prev_ext_config
+ or self.prev_ext_config['colorscheme'] != self.ext_config['colorscheme']
+ )
+ load_theme = (
+ load_theme
+ or not self.prev_ext_config
+ or self.prev_ext_config['theme'] != self.ext_config['theme']
+ )
+ self.prev_ext_config = self.ext_config
+
+ create_renderer = load_colors or load_colorscheme or load_theme or common_config_differs or ext_config_differs
+
+ if load_colors:
+ self._purge_configs('colors')
+ self.colors_config = self.load_colors_config()
+
+ if load_colorscheme or load_colors:
+ self._purge_configs('colorscheme')
+ if load_colorscheme:
+ self.colorscheme_config = self.load_colorscheme_config(self.ext_config['colorscheme'])
+ self.renderer_options['theme_kwargs']['colorscheme'] = (
+ Colorscheme(self.colorscheme_config, self.colors_config))
+
+ if load_theme:
+ self._purge_configs('theme')
+ self.renderer_options['theme_config'] = self.load_theme_config(self.ext_config.get('theme', 'default'))
+
+ if create_renderer:
+ Renderer = self.get_module_attr(self.renderer_module, 'renderer')
+ if not Renderer:
+ if hasattr(self, 'renderer'):
+ return
+ else:
+ raise ImportError('Failed to obtain renderer')
+
+ # Renderer updates configuration file via segments’ .startup thus it
+ # should be locked to prevent state when configuration was updated,
+ # but .render still uses old renderer.
+ try:
+ renderer = Renderer(**self.renderer_options)
+ except Exception as e:
+ self.exception('Failed to construct renderer object: {0}', str(e))
+ if not hasattr(self, 'renderer'):
+ raise
+ else:
+ self.renderer = renderer
+
+ default_log_stream = sys.stdout
+ '''Default stream for default log handler
+
+ Usually it is ``sys.stderr``, but there is sometimes a reason to prefer
+ ``sys.stdout`` or a custom file-like object. It is not supposed to be used
+ to write to some file.
+ '''
+
+ def setup_components(self, components):
+ '''Run component-specific setup
+
+ :param set components:
+ Set of the enabled components or None.
+
+ Should be overridden by subclasses.
+ '''
+ pass
+
+ @staticmethod
+ def get_config_paths():
+ '''Get configuration paths.
+
+ Should be overridden in subclasses in order to provide a way to override
+ used paths.
+
+ :return: list of paths
+ '''
+ return get_config_paths()
+
+ def load_config(self, cfg_path, cfg_type):
+ '''Load configuration and setup watches
+
+ :param str cfg_path:
+ Path to the configuration file without any powerline configuration
+ directory or ``.json`` suffix.
+ :param str cfg_type:
+ Configuration type. May be one of ``main`` (for ``config.json``
+ file), ``colors``, ``colorscheme``, ``theme``.
+
+ :return: dictionary with loaded configuration.
+ '''
+ return load_config(
+ cfg_path,
+ self.find_config_files,
+ self.config_loader,
+ self.cr_callbacks[cfg_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_main_config(self):
+ '''Get top-level configuration.
+
+ :return: dictionary with :ref:`top-level configuration <config-main>`.
+ '''
+ 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.
+
+ :param str name:
+ Name of the colorscheme to load.
+
+ :return: dictionary with :ref:`colorscheme configuration <config-colorschemes>`.
+ '''
+ levels = (
+ os.path.join('colorschemes', name),
+ os.path.join('colorschemes', self.ext, '__main__'),
+ os.path.join('colorschemes', self.ext, name),
+ )
+ 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 = self.theme_levels + (
+ os.path.join('themes', self.ext, name),
+ )
+ return self._load_hierarhical_config('theme', levels, (0, 1,))
+
+ def load_colors_config(self):
+ '''Get colorscheme.
+
+ :return: dictionary with :ref:`colors configuration <config-colors>`.
+ '''
+ return self.load_config('colors', 'colors')
+
+ @staticmethod
+ def get_local_themes(local_themes):
+ '''Get local themes. No-op here, to be overridden in subclasses if
+ required.
+
+ :param dict local_themes:
+ Usually accepts ``{matcher_name : theme_name}``. May also receive
+ None in case there is no local_themes configuration.
+
+ :return:
+ anything accepted by ``self.renderer.get_theme`` and processable by
+ ``self.renderer.add_local_theme``. Renderer module is determined by
+ ``__init__`` arguments, refer to its documentation.
+ '''
+ return None
+
+ def update_renderer(self):
+ '''Updates/creates a renderer if needed.'''
+ if self.run_loader_update:
+ self.config_loader.update()
+ cr_kwargs = None
+ with self.cr_kwargs_lock:
+ if self.cr_kwargs:
+ cr_kwargs = self.cr_kwargs.copy()
+ if cr_kwargs:
+ try:
+ self.create_renderer(**cr_kwargs)
+ except Exception as e:
+ self.exception('Failed to create renderer: {0}', str(e))
+ if hasattr(self, 'renderer'):
+ with self.cr_kwargs_lock:
+ self.cr_kwargs.clear()
+ else:
+ raise
+ else:
+ with self.cr_kwargs_lock:
+ self.cr_kwargs.clear()
+
+ def render(self, *args, **kwargs):
+ '''Update/create renderer if needed and pass all arguments further to
+ ``self.renderer.render()``.
+ '''
+ try:
+ self.update_renderer()
+ return self.renderer.render(*args, **kwargs)
+ except Exception as e:
+ exc = e
+ try:
+ self.exception('Failed to render: {0}', str(e))
+ except Exception as e:
+ exc = e
+ ret = FailedUnicode(safe_unicode(exc))
+ if kwargs.get('output_width', False):
+ ret = ret, len(ret)
+ return ret
+
+ def render_above_lines(self, *args, **kwargs):
+ '''Like .render(), but for ``self.renderer.render_above_lines()``
+ '''
+ try:
+ self.update_renderer()
+ for line in self.renderer.render_above_lines(*args, **kwargs):
+ yield line
+ except Exception as e:
+ exc = e
+ try:
+ self.exception('Failed to render: {0}', str(e))
+ except Exception as e:
+ exc = e
+ yield FailedUnicode(safe_unicode(exc))
+
+ def setup(self, *args, **kwargs):
+ '''Setup the environment to use powerline.
+
+ Must not be overridden by subclasses. This one only saves setup
+ arguments for :py:meth:`reload` method and calls :py:meth:`do_setup`.
+ '''
+ self.shutdown_event.clear()
+ self.setup_args = args
+ self.setup_kwargs.update(kwargs)
+ self.do_setup(*args, **kwargs)
+
+ @staticmethod
+ def do_setup():
+ '''Function that does initialization
+
+ Should be overridden by subclasses. May accept any number of regular or
+ keyword arguments.
+ '''
+ pass
+
+ def reload(self):
+ '''Reload powerline after update.
+
+ Should handle most (but not all) powerline updates.
+
+ Purges out all powerline modules and modules imported by powerline for
+ segment and matcher functions. Requires defining ``setup`` function that
+ updates reference to main powerline object.
+
+ .. warning::
+ Not guaranteed to work properly, use it at your own risk. It
+ may break your python code.
+ '''
+ import sys
+ modules = self.imported_modules | set((module for module in sys.modules if module.startswith('powerline')))
+ modules_holder = []
+ for module in modules:
+ try:
+ # Needs to hold module to prevent garbage collecting until they
+ # are all reloaded.
+ modules_holder.append(sys.modules.pop(module))
+ except KeyError:
+ pass
+ PowerlineClass = getattr(__import__(self.__module__, fromlist=(self.__class__.__name__,)), self.__class__.__name__)
+ self.shutdown(set_event=True)
+ init_args, init_kwargs = self.init_args
+ powerline = PowerlineClass(*init_args, **init_kwargs)
+ powerline.setup(*self.setup_args, **self.setup_kwargs)
+
+ def shutdown(self, set_event=True):
+ '''Shut down all background threads.
+
+ :param bool set_event:
+ Set ``shutdown_event`` and call ``renderer.shutdown`` which should
+ shut down all threads. Set it to False unless you are exiting an
+ application.
+
+ If set to False this does nothing more then resolving reference
+ cycle ``powerline → config_loader → bound methods → powerline`` by
+ unsubscribing from config_loader events.
+ '''
+ if set_event:
+ self.shutdown_event.set()
+ try:
+ self.renderer.shutdown()
+ except AttributeError:
+ pass
+ functions = tuple(self.cr_callbacks.values())
+ self.config_loader.unregister_functions(set(functions))
+ self.config_loader.unregister_missing(set(((self.find_config_files, function) for function in functions)))
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.shutdown()
+
+ def exception(self, msg, *args, **kwargs):
+ if 'prefix' not in kwargs:
+ kwargs['prefix'] = 'powerline'
+ exception = kwargs.pop('exception', None)
+ pl = getattr(self, 'pl', None) or get_fallback_logger(self.default_log_stream)
+ if exception:
+ try:
+ reraise(exception)
+ except Exception:
+ return pl.exception(msg, *args, **kwargs)
+ return pl.exception(msg, *args, **kwargs)
diff --git a/powerline/bindings/__init__.py b/powerline/bindings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/bindings/__init__.py
diff --git a/powerline/bindings/awesome/powerline-awesome.py b/powerline/bindings/awesome/powerline-awesome.py
new file mode 100755
index 0000000..500d47d
--- /dev/null
+++ b/powerline/bindings/awesome/powerline-awesome.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+
+from powerline.bindings.wm import DEFAULT_UPDATE_INTERVAL
+from powerline.bindings.wm.awesome import run
+
+
+def main():
+ try:
+ interval = float(sys.argv[1])
+ except IndexError:
+ interval = DEFAULT_UPDATE_INTERVAL
+ run(interval=interval)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/powerline/bindings/awesome/powerline.lua b/powerline/bindings/awesome/powerline.lua
new file mode 100644
index 0000000..470901f
--- /dev/null
+++ b/powerline/bindings/awesome/powerline.lua
@@ -0,0 +1,15 @@
+local wibox = require('wibox')
+local awful = require('awful')
+
+powerline_widget = wibox.widget.textbox()
+powerline_widget:set_align('right')
+
+function powerline(mode, widget) end
+
+if string.find(awesome.version, 'v4') then
+ awful.spawn.with_shell('powerline-daemon -q')
+ awful.spawn.with_shell('powerline wm.awesome')
+else
+ awful.util.spawn_with_shell('powerline-daemon -q')
+ awful.util.spawn_with_shell('powerline wm.awesome')
+end
diff --git a/powerline/bindings/bar/powerline-bar.py b/powerline/bindings/bar/powerline-bar.py
new file mode 100755
index 0000000..71e8ae3
--- /dev/null
+++ b/powerline/bindings/bar/powerline-bar.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import sys
+import time
+
+from threading import Lock, Timer
+from argparse import ArgumentParser
+
+from powerline.lemonbar import LemonbarPowerline
+from powerline.lib.encoding import get_unicode_writer
+from powerline.bindings.wm import DEFAULT_UPDATE_INTERVAL
+
+
+if __name__ == '__main__':
+ parser = ArgumentParser(description='Powerline lemonbar bindings.')
+ parser.add_argument(
+ '--i3', action='store_true',
+ help='Subscribe for i3 events.'
+ )
+ args = parser.parse_args()
+ powerline = LemonbarPowerline()
+ powerline.update_renderer()
+ powerline.pl.warn("The 'bar' bindings are deprecated, please switch to 'lemonbar'")
+ lock = Lock()
+ modes = ['default']
+ write = get_unicode_writer(encoding='utf-8')
+
+ def render(reschedule=False):
+ if reschedule:
+ Timer(DEFAULT_UPDATE_INTERVAL, render, kwargs={'reschedule': True}).start()
+
+ global lock
+ with lock:
+ write(powerline.render(mode=modes[0]))
+ write('\n')
+ sys.stdout.flush()
+
+ def update(evt):
+ modes[0] = evt.change
+ render()
+
+ render(reschedule=True)
+
+ if args.i3:
+ try:
+ import i3ipc
+ except ImportError:
+ import i3
+ i3.Subscription(lambda evt, data, sub: print(render()), 'workspace')
+ else:
+ conn = i3ipc.Connection()
+ conn.on('workspace::focus', lambda conn, evt: render())
+ conn.on('mode', lambda conn, evt: update(evt))
+ conn.main()
+
+ while True:
+ time.sleep(1e8)
diff --git a/powerline/bindings/bash/powerline.sh b/powerline/bindings/bash/powerline.sh
new file mode 100644
index 0000000..2c0943c
--- /dev/null
+++ b/powerline/bindings/bash/powerline.sh
@@ -0,0 +1,153 @@
+_powerline_columns_fallback() {
+ if command -v stty &>/dev/null ; then
+ local cols="$(stty size 2>/dev/null)"
+ if ! test -z "$cols" ; then
+ echo "${cols#* }"
+ return 0
+ fi
+ fi
+ echo 0
+ return 0
+}
+
+_powerline_tmux_pane() {
+ echo "${TMUX_PANE:-`TMUX="$_POWERLINE_TMUX" tmux display -p "#D"`}" | \
+ tr -d ' %'
+}
+
+_powerline_tmux_setenv() {
+ TMUX="$_POWERLINE_TMUX" tmux setenv -g TMUX_"$1"_`_powerline_tmux_pane` "$2"
+ TMUX="$_POWERLINE_TMUX" tmux refresh -S
+}
+
+_powerline_tmux_set_pwd() {
+ if test "$_POWERLINE_SAVED_PWD" != "$PWD" ; then
+ _POWERLINE_SAVED_PWD="$PWD"
+ _powerline_tmux_setenv PWD "$PWD"
+ fi
+}
+
+_powerline_return() {
+ return $1
+}
+
+_POWERLINE_HAS_PIPESTATUS="$(
+ _powerline_return 0 | _powerline_return 43
+ test "${PIPESTATUS[*]}" = "0 43"
+ echo "$?"
+)"
+
+_powerline_has_pipestatus() {
+ return $_POWERLINE_HAS_PIPESTATUS
+}
+
+_powerline_status_wrapper() {
+ local last_exit_code=$? last_pipe_status=( "${PIPESTATUS[@]}" )
+
+ if ! _powerline_has_pipestatus \
+ || test "${#last_pipe_status[@]}" -eq "0" \
+ || test "$last_exit_code" != "${last_pipe_status[$(( ${#last_pipe_status[@]} - 1 ))]}" ; then
+ last_pipe_status=()
+ fi
+ "$@" $last_exit_code "${last_pipe_status[*]}"
+ return $last_exit_code
+}
+
+_powerline_add_status_wrapped_command() {
+ local action="$1" ; shift
+ local cmd="$1" ; shift
+ full_cmd="_powerline_status_wrapper $cmd"
+ if test "$action" = "append" ; then
+ PROMPT_COMMAND="$PROMPT_COMMAND"$'\n'"$full_cmd"
+ else
+ PROMPT_COMMAND="$full_cmd"$'\n'"$PROMPT_COMMAND"
+ fi
+}
+
+_powerline_tmux_set_columns() {
+ _powerline_tmux_setenv COLUMNS "${COLUMNS:-`_powerline_columns_fallback`}"
+}
+
+_powerline_init_tmux_support() {
+ if test -n "$TMUX" && tmux refresh -S &>/dev/null ; then
+ # TMUX variable may be unset to create new tmux session inside this one
+ _POWERLINE_TMUX="$TMUX"
+
+ trap '_powerline_tmux_set_columns' WINCH
+ _powerline_tmux_set_columns
+
+ test "$PROMPT_COMMAND" != "${PROMPT_COMMAND/_powerline_tmux_set_pwd}" \
+ || _powerline_add_status_wrapped_command append _powerline_tmux_set_pwd
+ fi
+}
+
+_powerline_local_prompt() {
+ # Arguments:
+ # 1: side
+ # 2: renderer_module arg
+ # 3: last_exit_code
+ # 4: last_pipe_status
+ # 5: jobnum
+ # 6: local theme
+ "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \
+ $2 \
+ --last-exit-code=$3 \
+ --last-pipe-status="$4" \
+ --jobnum=$5 \
+ --renderer-arg="client_id=$$" \
+ --renderer-arg="local_theme=$6"
+}
+
+_powerline_prompt() {
+ # Arguments:
+ # 1: side
+ # 2: last_exit_code
+ # 3: last_pipe_status
+ # 4: jobnum
+ "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \
+ --width="${COLUMNS:-$(_powerline_columns_fallback)}" \
+ -r.bash \
+ --last-exit-code=$2 \
+ --last-pipe-status="$3" \
+ --jobnum=$4 \
+ --renderer-arg="client_id=$$"
+}
+
+_powerline_set_prompt() {
+ local last_exit_code=$1 ; shift
+ local last_pipe_status=$1 ; shift
+ local jobnum="$(jobs -p|wc -l)"
+ PS1="$(_powerline_prompt aboveleft $last_exit_code "$last_pipe_status" $jobnum)"
+ if test -n "$POWERLINE_SHELL_CONTINUATION$POWERLINE_BASH_CONTINUATION" ; then
+ PS2="$(_powerline_local_prompt left -r.bash $last_exit_code "$last_pipe_status" $jobnum continuation)"
+ fi
+ if test -n "$POWERLINE_SHELL_SELECT$POWERLINE_BASH_SELECT" ; then
+ PS3="$(_powerline_local_prompt left '' $last_exit_code "$last_pipe_status" $jobnum select)"
+ fi
+}
+
+_powerline_setup_prompt() {
+ VIRTUAL_ENV_DISABLE_PROMPT=1
+ if test -z "${POWERLINE_COMMAND}" ; then
+ POWERLINE_COMMAND="$("$POWERLINE_CONFIG_COMMAND" shell command)"
+ fi
+ test "$PROMPT_COMMAND" != "${PROMPT_COMMAND%_powerline_set_prompt*}" \
+ || _powerline_add_status_wrapped_command prepend _powerline_set_prompt
+ PS2="$(_powerline_local_prompt left -r.bash 0 0 0 continuation)"
+ PS3="$(_powerline_local_prompt left '' 0 0 0 select)"
+}
+
+if test -z "${POWERLINE_CONFIG_COMMAND}" ; then
+ if command -v powerline-config >/dev/null ; then
+ POWERLINE_CONFIG_COMMAND=powerline-config
+ else
+ POWERLINE_CONFIG_COMMAND="$(dirname "$BASH_SOURCE")/../../../scripts/powerline-config"
+ fi
+fi
+
+if "${POWERLINE_CONFIG_COMMAND}" shell --shell=bash uses prompt ; then
+ _powerline_setup_prompt
+fi
+if "${POWERLINE_CONFIG_COMMAND}" shell --shell=bash uses tmux ; then
+ _powerline_init_tmux_support
+fi
diff --git a/powerline/bindings/config.py b/powerline/bindings/config.py
new file mode 100644
index 0000000..3100633
--- /dev/null
+++ b/powerline/bindings/config.py
@@ -0,0 +1,286 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import re
+import sys
+import subprocess
+import shlex
+
+from powerline.config import POWERLINE_ROOT, TMUX_CONFIG_DIRECTORY
+from powerline.lib.config import ConfigLoader
+from powerline import generate_config_finder, load_config, create_logger, finish_common_config
+from powerline.shell import ShellPowerline
+from powerline.lib.shell import which
+from powerline.bindings.tmux import (TmuxVersionInfo, run_tmux_command, set_tmux_environment, get_tmux_version,
+ source_tmux_file)
+from powerline.lib.encoding import get_preferred_output_encoding
+from powerline.renderers.tmux import attrs_to_tmux_attrs
+from powerline.commands.main import finish_args
+
+
+CONFIG_FILE_NAME = re.compile(r'powerline_tmux_(?P<major>\d+)\.(?P<minor>\d+)(?P<suffix>[a-z]+)?(?:_(?P<mod>plus|minus))?\.conf')
+CONFIG_MATCHERS = {
+ None: (lambda a, b: a.major == b.major and a.minor == b.minor),
+ 'plus': (lambda a, b: a[:2] <= b[:2]),
+ 'minus': (lambda a, b: a[:2] >= b[:2]),
+}
+CONFIG_PRIORITY = {
+ None: 3,
+ 'plus': 2,
+ 'minus': 1,
+}
+
+
+def list_all_tmux_configs():
+ '''List all version-specific tmux configuration files'''
+ for root, dirs, files in os.walk(TMUX_CONFIG_DIRECTORY):
+ dirs[:] = ()
+ for fname in files:
+ match = CONFIG_FILE_NAME.match(fname)
+ if match:
+ assert match.group('suffix') is None
+ yield (
+ os.path.join(root, fname),
+ CONFIG_MATCHERS[match.group('mod')],
+ CONFIG_PRIORITY[match.group('mod')],
+ TmuxVersionInfo(
+ int(match.group('major')),
+ int(match.group('minor')),
+ match.group('suffix'),
+ ),
+ )
+
+
+def get_tmux_configs(version):
+ '''Get tmux configuration suffix given parsed tmux version
+
+ :param TmuxVersionInfo version: Parsed tmux version.
+ '''
+ for fname, matcher, priority, file_version in list_all_tmux_configs():
+ if matcher(file_version, version):
+ yield (fname, priority + file_version.minor * 10 + file_version.major * 10000)
+
+
+def source_tmux_files(pl, args, tmux_version=None, source_tmux_file=source_tmux_file):
+ '''Source relevant version-specific tmux configuration files
+
+ Files are sourced in the following order:
+ * First relevant files with older versions are sourced.
+ * If files for same versions are to be sourced then first _minus files are
+ sourced, then _plus files and then files without _minus or _plus suffixes.
+ '''
+ tmux_version = tmux_version or get_tmux_version(pl)
+ source_tmux_file(os.path.join(TMUX_CONFIG_DIRECTORY, 'powerline-base.conf'))
+ for fname, priority in sorted(get_tmux_configs(tmux_version), key=(lambda v: v[1])):
+ source_tmux_file(fname)
+ if not os.environ.get('POWERLINE_COMMAND'):
+ cmd = deduce_command()
+ if cmd:
+ set_tmux_environment('POWERLINE_COMMAND', deduce_command(), remove=False)
+ try:
+ run_tmux_command('refresh-client')
+ except subprocess.CalledProcessError:
+ # On tmux-2.0 this command may fail for whatever reason. Since it is
+ # critical just ignore the failure.
+ pass
+
+
+class EmptyArgs(object):
+ def __init__(self, ext, config_path):
+ self.ext = [ext]
+ self.side = 'left'
+ self.config_path = None
+
+ def __getattr__(self, attr):
+ return None
+
+
+def init_tmux_environment(pl, args, set_tmux_environment=set_tmux_environment):
+ '''Initialize tmux environment from tmux configuration
+ '''
+ powerline = ShellPowerline(finish_args(None, os.environ, EmptyArgs('tmux', args.config_path)))
+ # TODO Move configuration files loading out of Powerline object and use it
+ # directly
+ powerline.update_renderer()
+ # FIXME Use something more stable then `theme_kwargs`
+ colorscheme = powerline.renderer_options['theme_kwargs']['colorscheme']
+
+ def get_highlighting(group):
+ return colorscheme.get_highlighting([group], None)
+
+ for varname, highlight_group in (
+ ('_POWERLINE_BACKGROUND_COLOR', 'background'),
+ ('_POWERLINE_ACTIVE_WINDOW_STATUS_COLOR', 'active_window_status'),
+ ('_POWERLINE_WINDOW_STATUS_COLOR', 'window_status'),
+ ('_POWERLINE_ACTIVITY_STATUS_COLOR', 'activity_status'),
+ ('_POWERLINE_BELL_STATUS_COLOR', 'bell_status'),
+ ('_POWERLINE_WINDOW_COLOR', 'window'),
+ ('_POWERLINE_WINDOW_DIVIDER_COLOR', 'window:divider'),
+ ('_POWERLINE_WINDOW_CURRENT_COLOR', 'window:current'),
+ ('_POWERLINE_WINDOW_NAME_COLOR', 'window_name'),
+ ('_POWERLINE_SESSION_COLOR', 'session'),
+ ):
+ highlight = get_highlighting(highlight_group)
+ set_tmux_environment(varname, powerline.renderer.hlstyle(**highlight)[2:-1])
+ for varname, prev_group, next_group in (
+ ('_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_COLOR', 'window', 'window:current'),
+ ('_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_NEXT_COLOR', 'window:current', 'window'),
+ ('_POWERLINE_SESSION_HARD_DIVIDER_NEXT_COLOR', 'session', 'background'),
+ ):
+ prev_highlight = get_highlighting(prev_group)
+ next_highlight = get_highlighting(next_group)
+ set_tmux_environment(
+ varname,
+ powerline.renderer.hlstyle(
+ fg=prev_highlight['bg'],
+ bg=next_highlight['bg'],
+ attrs=0,
+ )[2:-1]
+ )
+ for varname, attr, group in (
+ ('_POWERLINE_ACTIVE_WINDOW_FG', 'fg', 'active_window_status'),
+ ('_POWERLINE_WINDOW_STATUS_FG', 'fg', 'window_status'),
+ ('_POWERLINE_ACTIVITY_STATUS_FG', 'fg', 'activity_status'),
+ ('_POWERLINE_ACTIVITY_STATUS_ATTR', 'attrs', 'activity_status'),
+ ('_POWERLINE_BELL_STATUS_FG', 'fg', 'bell_status'),
+ ('_POWERLINE_BELL_STATUS_ATTR', 'attrs', 'bell_status'),
+ ('_POWERLINE_BACKGROUND_FG', 'fg', 'background'),
+ ('_POWERLINE_BACKGROUND_BG', 'bg', 'background'),
+ ('_POWERLINE_SESSION_FG', 'fg', 'session'),
+ ('_POWERLINE_SESSION_BG', 'bg', 'session'),
+ ('_POWERLINE_SESSION_ATTR', 'attrs', 'session'),
+ ('_POWERLINE_SESSION_PREFIX_FG', 'fg', 'session:prefix'),
+ ('_POWERLINE_SESSION_PREFIX_BG', 'bg', 'session:prefix'),
+ ('_POWERLINE_SESSION_PREFIX_ATTR', 'attrs', 'session:prefix'),
+ ):
+ if attr == 'attrs':
+ attrs = attrs_to_tmux_attrs(get_highlighting(group)[attr])
+ set_tmux_environment(varname, ']#['.join(attrs))
+ set_tmux_environment(varname + '_LEGACY', (','.join(
+ # Tmux-1.6 does not accept no… attributes in
+ # window-status-…-attr options.
+ (attr for attr in attrs if not attr.startswith('no')))
+ # But it does not support empty attributes as well.
+ or 'none'))
+ else:
+ if powerline.common_config['term_truecolor']:
+ set_tmux_environment(varname, '#{0:06x}'.format(get_highlighting(group)[attr][1]))
+ else:
+ set_tmux_environment(varname, 'colour' + str(get_highlighting(group)[attr][0]))
+
+ left_dividers = powerline.renderer.theme.dividers['left']
+ set_tmux_environment('_POWERLINE_LEFT_HARD_DIVIDER', left_dividers['hard'])
+ set_tmux_environment('_POWERLINE_LEFT_SOFT_DIVIDER', left_dividers['soft'])
+ set_tmux_environment('_POWERLINE_LEFT_HARD_DIVIDER_SPACES', (
+ ' ' * powerline.renderer.strwidth(left_dividers['hard'])))
+
+
+TMUX_VAR_RE = re.compile('\$(_POWERLINE_\w+)')
+
+
+def tmux_setup(pl, args):
+ tmux_environ = {}
+ tmux_version = get_tmux_version(pl)
+
+ def set_tmux_environment_nosource(varname, value, remove=True):
+ tmux_environ[varname] = value
+
+ def replace_cb(match):
+ return tmux_environ[match.group(1)]
+
+ def replace_env(s):
+ return TMUX_VAR_RE.subn(replace_cb, s)[0]
+
+ def source_tmux_file_nosource(fname):
+ with open(fname) as fd:
+ for line in fd:
+ if line.startswith('#') or line == '\n':
+ continue
+ args = shlex.split(line)
+ args = [args[0]] + [replace_env(arg) for arg in args[1:]]
+ run_tmux_command(*args)
+
+ if args.source is None:
+ args.source = tmux_version < (1, 9)
+
+ if args.source:
+ ste = set_tmux_environment
+ stf = source_tmux_file
+ else:
+ ste = set_tmux_environment_nosource
+ stf = source_tmux_file_nosource
+
+ init_tmux_environment(pl, args, set_tmux_environment=ste)
+ source_tmux_files(pl, args, tmux_version=tmux_version, source_tmux_file=stf)
+
+
+def get_main_config(args):
+ find_config_files = generate_config_finder()
+ config_loader = ConfigLoader(run_once=True)
+ return load_config('config', find_config_files, config_loader)
+
+
+def create_powerline_logger(args):
+ config = get_main_config(args)
+ common_config = finish_common_config(get_preferred_output_encoding(), config['common'])
+ logger, pl, get_module_attr = create_logger(common_config)
+ return pl
+
+
+def check_command(cmd):
+ if which(cmd):
+ return cmd
+
+
+def deduce_command():
+ '''Deduce which command to use for ``powerline``
+
+ Candidates:
+
+ * ``powerline``. Present only when installed system-wide.
+ * ``{powerline_root}/scripts/powerline``. Present after ``pip install -e``
+ was run and C client was compiled (in this case ``pip`` does not install
+ binary file).
+ * ``{powerline_root}/client/powerline.sh``. Useful when ``sh``, ``sed`` and
+ ``socat`` are present, but ``pip`` or ``setup.py`` was not run.
+ * ``{powerline_root}/client/powerline.py``. Like above, but when one of
+ ``sh``, ``sed`` and ``socat`` was not present.
+ * ``powerline-render``. Should not really ever be used.
+ * ``{powerline_root}/scripts/powerline-render``. Same.
+ '''
+ return (
+ None
+ or check_command('powerline')
+ or check_command(os.path.join(POWERLINE_ROOT, 'scripts', 'powerline'))
+ or ((which('sh') and which('sed') and which('socat'))
+ and check_command(os.path.join(POWERLINE_ROOT, 'client', 'powerline.sh')))
+ or check_command(os.path.join(POWERLINE_ROOT, 'client', 'powerline.py'))
+ or check_command('powerline-render')
+ or check_command(os.path.join(POWERLINE_ROOT, 'scripts', 'powerline-render'))
+ )
+
+
+def shell_command(pl, args):
+ cmd = deduce_command()
+ if cmd:
+ print(cmd)
+ else:
+ sys.exit(1)
+
+
+def uses(pl, args):
+ component = args.component
+ if not component:
+ raise ValueError('Must specify component')
+ shell = args.shell
+ template = 'POWERLINE_NO_{shell}_{component}'
+ for sh in (shell, 'shell') if shell else ('shell'):
+ varname = template.format(shell=sh.upper(), component=component.upper())
+ if os.environ.get(varname):
+ sys.exit(1)
+ config = get_main_config(args)
+ if component in config.get('ext', {}).get('shell', {}).get('components', ('tmux', 'prompt')):
+ sys.exit(0)
+ else:
+ sys.exit(1)
diff --git a/powerline/bindings/fish/powerline-setup.fish b/powerline/bindings/fish/powerline-setup.fish
new file mode 100644
index 0000000..f5d02d6
--- /dev/null
+++ b/powerline/bindings/fish/powerline-setup.fish
@@ -0,0 +1,100 @@
+function powerline-setup
+ function _powerline_columns
+ if which stty >/dev/null
+ if stty size >/dev/null
+ stty size | cut -d' ' -f2
+ return 0
+ end
+ end
+ echo 0
+ return 0
+ end
+
+ if test -z "$POWERLINE_CONFIG_COMMAND"
+ if which powerline-config >/dev/null
+ set -g POWERLINE_CONFIG_COMMAND powerline-config
+ else
+ set -g POWERLINE_CONFIG_COMMAND (dirname (status -f))/../../../scripts/powerline-config
+ end
+ end
+
+ if env $POWERLINE_CONFIG_COMMAND shell --shell=fish uses prompt
+ if test -z "$POWERLINE_COMMAND"
+ set -g POWERLINE_COMMAND (env $POWERLINE_CONFIG_COMMAND shell command)
+ end
+ function _powerline_set_default_mode --on-variable fish_key_bindings
+ if test $fish_key_bindings != fish_vi_key_bindings
+ set -g _POWERLINE_DEFAULT_MODE default
+ else
+ set -g -e _POWERLINE_DEFAULT_MODE
+ end
+ end
+ function _powerline_update --on-variable POWERLINE_COMMAND
+ set -l addargs "--last-exit-code=\$status"
+ set -l addargs "$addargs --last-pipe-status=\$status"
+ set -l addargs "$addargs --jobnum=(jobs -p | wc -l)"
+ # One random value has an 1/32767 = 0.0031% probability of having
+ # the same value in two shells
+ set -l addargs "$addargs --renderer-arg=client_id="(random)
+ set -l addargs "$addargs --width=\$_POWERLINE_COLUMNS"
+ set -l addargs "$addargs --renderer-arg=mode=\$fish_bind_mode"
+ set -l addargs "$addargs --renderer-arg=default_mode=\$_POWERLINE_DEFAULT_MODE"
+ set -l promptside
+ set -l rpromptpast
+ set -l columnsexpr
+ if test -z "$POWERLINE_NO_FISH_ABOVE$POWERLINE_NO_SHELL_ABOVE"
+ set promptside aboveleft
+ set rpromptpast 'echo -n " "'
+ set columnsexpr '(math (_powerline_columns) - 1)'
+ else
+ set promptside left
+ set rpromptpast
+ set columnsexpr '(_powerline_columns)'
+ end
+ echo "
+ function fish_prompt
+ env \$POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS shell $promptside $addargs
+ end
+ function fish_right_prompt
+ env \$POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS shell right $addargs
+ $rpromptpast
+ end
+ function fish_mode_prompt
+ end
+ function _powerline_set_columns --on-signal WINCH
+ set -g _POWERLINE_COLUMNS $columnsexpr
+ end
+ " | source
+ _powerline_set_columns
+ end
+ _powerline_set_default_mode
+ _powerline_update
+ end
+ if env $POWERLINE_CONFIG_COMMAND shell --shell=fish uses tmux
+ if test -n "$TMUX"
+ if tmux refresh -S ^/dev/null
+ set -g _POWERLINE_TMUX "$TMUX"
+ function _powerline_tmux_pane
+ if test -z "$TMUX_PANE"
+ env TMUX="$_POWERLINE_TMUX" tmux display -p "#D" | tr -d ' %'
+ else
+ echo "$TMUX_PANE" | tr -d ' %'
+ end
+ end
+ function _powerline_tmux_setenv
+ env TMUX="$_POWERLINE_TMUX" tmux setenv -g TMUX_$argv[1]_(_powerline_tmux_pane) "$argv[2]"
+ env TMUX="$_POWERLINE_TMUX" tmux refresh -S
+ end
+ function _powerline_tmux_set_pwd --on-variable PWD
+ _powerline_tmux_setenv PWD "$PWD"
+ end
+ function _powerline_tmux_set_columns --on-signal WINCH
+ _powerline_tmux_setenv COLUMNS (_powerline_columns)
+ end
+ _powerline_tmux_set_columns
+ _powerline_tmux_set_pwd
+ end
+ end
+ end
+end
+# vim: ft=fish
diff --git a/powerline/bindings/i3/powerline-i3.py b/powerline/bindings/i3/powerline-i3.py
new file mode 100755
index 0000000..f44e928
--- /dev/null
+++ b/powerline/bindings/i3/powerline-i3.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import time
+
+from threading import Lock
+
+from powerline.bindings.wm import get_i3_connection, i3_subscribe
+
+from powerline import Powerline
+from powerline.lib.monotonic import monotonic
+
+
+class I3Powerline(Powerline):
+ '''Powerline child for i3bar
+
+ Currently only changes the default log target.
+ '''
+ default_log_stream = sys.stderr
+
+
+if __name__ == '__main__':
+ name = 'wm'
+ if len(sys.argv) > 1:
+ name = sys.argv[1]
+
+ powerline = I3Powerline(name, renderer_module='i3bar')
+ powerline.update_renderer()
+
+ interval = 0.5
+
+ print ('{"version": 1}')
+ print ('[')
+ print ('[]')
+
+ lock = Lock()
+
+ def render(event=None, data=None, sub=None):
+ global lock
+ with lock:
+ print (',[' + powerline.render()[:-1] + ']')
+ sys.stdout.flush()
+
+ i3 = get_i3_connection()
+ i3_subscribe(i3, 'workspace', render)
+
+ while True:
+ start_time = monotonic()
+ render()
+ time.sleep(max(interval - (monotonic() - start_time), 0.1))
diff --git a/powerline/bindings/ipython/__init__.py b/powerline/bindings/ipython/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/bindings/ipython/__init__.py
diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py
new file mode 100644
index 0000000..3213c51
--- /dev/null
+++ b/powerline/bindings/ipython/post_0_11.py
@@ -0,0 +1,126 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division,
+ absolute_import, print_function)
+
+from weakref import ref
+from warnings import warn
+
+try:
+ from IPython.core.prompts import PromptManager
+ has_prompt_manager = True
+except ImportError:
+ has_prompt_manager = False
+from IPython.core.magic import Magics, magics_class, line_magic
+
+from powerline.ipython import IPythonPowerline, IPythonInfo
+
+if has_prompt_manager:
+ from powerline.ipython import RewriteResult
+
+
+@magics_class
+class PowerlineMagics(Magics):
+ def __init__(self, ip, powerline):
+ super(PowerlineMagics, self).__init__(ip)
+ self._powerline = powerline
+
+ @line_magic
+ def powerline(self, line):
+ if line == 'reload':
+ self._powerline.reload()
+ else:
+ raise ValueError('Expected `reload`, but got {0}'.format(line))
+
+
+old_prompt_manager = None
+
+
+class ShutdownHook(object):
+ def __init__(self, ip):
+ self.powerline = lambda: None
+ ip.hooks.shutdown_hook.add(self)
+
+ def __call__(self):
+ from IPython.core.hooks import TryNext
+ powerline = self.powerline()
+ if powerline is not None:
+ powerline.shutdown()
+ raise TryNext()
+
+
+if has_prompt_manager:
+ class PowerlinePromptManager(PromptManager):
+ def __init__(self, powerline, shell):
+ self.powerline = powerline
+ self.powerline_segment_info = IPythonInfo(shell)
+ self.shell = shell
+
+ def render(self, name, color=True, *args, **kwargs):
+ res = self.powerline.render(
+ is_prompt=name.startswith('in'),
+ side='left',
+ output_width=True,
+ output_raw=not color,
+ matcher_info=name,
+ segment_info=self.powerline_segment_info,
+ )
+ self.txtwidth = res[-1]
+ self.width = res[-1]
+ ret = res[0] if color else res[1]
+ if name == 'rewrite':
+ return RewriteResult(ret)
+ else:
+ return ret
+
+ class ConfigurableIPythonPowerline(IPythonPowerline):
+ def init(self, ip):
+ config = ip.config.Powerline
+ self.config_overrides = config.get('config_overrides')
+ self.theme_overrides = config.get('theme_overrides', {})
+ self.config_paths = config.get('config_paths')
+ if has_prompt_manager:
+ renderer_module = '.pre_5'
+ else:
+ renderer_module = '.since_7'
+ super(ConfigurableIPythonPowerline, self).init(
+ renderer_module=renderer_module)
+
+ def do_setup(self, ip, shutdown_hook):
+ global old_prompt_manager
+
+ if old_prompt_manager is None:
+ old_prompt_manager = ip.prompt_manager
+ prompt_manager = PowerlinePromptManager(
+ powerline=self,
+ shell=ip.prompt_manager.shell,
+ )
+ ip.prompt_manager = prompt_manager
+
+ magics = PowerlineMagics(ip, self)
+ shutdown_hook.powerline = ref(self)
+ ip.register_magics(magics)
+
+
+def load_ipython_extension(ip):
+ if has_prompt_manager:
+ shutdown_hook = ShutdownHook(ip)
+ powerline = ConfigurableIPythonPowerline(ip)
+ powerline.setup(ip, shutdown_hook)
+ else:
+ from powerline.bindings.ipython.since_7 import PowerlinePrompts
+ ip.prompts_class = PowerlinePrompts
+ ip.prompts = PowerlinePrompts(ip)
+ warn(DeprecationWarning(
+ 'post_0_11 extension is deprecated since IPython 5, use\n'
+ ' from powerline.bindings.ipython.since_7 import PowerlinePrompts\n'
+ ' c.TerminalInteractiveShell.prompts_class = PowerlinePrompts\n'
+ 'or check: \n'
+ 'https://powerline.readthedocs.io/en/master/usage/other.html\n'
+ ))
+
+
+def unload_ipython_extension(ip):
+ global old_prompt_manager
+ if old_prompt_manager is not None:
+ ip.prompt_manager = old_prompt_manager
+ old_prompt_manager = None
diff --git a/powerline/bindings/ipython/pre_0_11.py b/powerline/bindings/ipython/pre_0_11.py
new file mode 100644
index 0000000..2bd8095
--- /dev/null
+++ b/powerline/bindings/ipython/pre_0_11.py
@@ -0,0 +1,146 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import re
+
+from weakref import ref
+
+from IPython.Prompts import BasePrompt
+from IPython.ipapi import get as get_ipython
+from IPython.ipapi import TryNext
+
+from powerline.ipython import IPythonPowerline, RewriteResult
+from powerline.lib.unicode import string
+
+
+class IPythonInfo(object):
+ def __init__(self, cache):
+ self._cache = cache
+
+ @property
+ def prompt_count(self):
+ return self._cache.prompt_count
+
+
+class PowerlinePrompt(BasePrompt):
+ def __init__(self, powerline, powerline_last_in, old_prompt):
+ self.powerline = powerline
+ self.powerline_last_in = powerline_last_in
+ self.powerline_segment_info = IPythonInfo(old_prompt.cache)
+ self.cache = old_prompt.cache
+ if hasattr(old_prompt, 'sep'):
+ self.sep = old_prompt.sep
+ self.pad_left = False
+
+ def __str__(self):
+ self.set_p_str()
+ return string(self.p_str)
+
+ def set_p_str(self):
+ self.p_str, self.p_str_nocolor, self.powerline_prompt_width = (
+ self.powerline.render(
+ is_prompt=self.powerline_is_prompt,
+ side='left',
+ output_raw=True,
+ output_width=True,
+ segment_info=self.powerline_segment_info,
+ matcher_info=self.powerline_prompt_type,
+ )
+ )
+
+ @staticmethod
+ def set_colors():
+ pass
+
+
+class PowerlinePrompt1(PowerlinePrompt):
+ powerline_prompt_type = 'in'
+ powerline_is_prompt = True
+ rspace = re.compile(r'(\s*)$')
+
+ def __str__(self):
+ self.cache.prompt_count += 1
+ self.set_p_str()
+ self.cache.last_prompt = self.p_str_nocolor.split('\n')[-1]
+ return string(self.p_str)
+
+ def set_p_str(self):
+ super(PowerlinePrompt1, self).set_p_str()
+ self.nrspaces = len(self.rspace.search(self.p_str_nocolor).group())
+ self.powerline_last_in['nrspaces'] = self.nrspaces
+
+ def auto_rewrite(self):
+ return RewriteResult(self.powerline.render(
+ is_prompt=False,
+ side='left',
+ matcher_info='rewrite',
+ segment_info=self.powerline_segment_info) + (' ' * self.nrspaces)
+ )
+
+
+class PowerlinePromptOut(PowerlinePrompt):
+ powerline_prompt_type = 'out'
+ powerline_is_prompt = False
+
+ def set_p_str(self):
+ super(PowerlinePromptOut, self).set_p_str()
+ spaces = ' ' * self.powerline_last_in['nrspaces']
+ self.p_str += spaces
+ self.p_str_nocolor += spaces
+
+
+class PowerlinePrompt2(PowerlinePromptOut):
+ powerline_prompt_type = 'in2'
+ powerline_is_prompt = True
+
+
+class ConfigurableIPythonPowerline(IPythonPowerline):
+ def init(self, config_overrides=None, theme_overrides={}, config_paths=None):
+ self.config_overrides = config_overrides
+ self.theme_overrides = theme_overrides
+ self.config_paths = config_paths
+ super(ConfigurableIPythonPowerline, self).init(renderer_module='.pre_5')
+
+ def ipython_magic(self, ip, parameter_s=''):
+ if parameter_s == 'reload':
+ self.reload()
+ else:
+ raise ValueError('Expected `reload`, but got {0}'.format(parameter_s))
+
+ def do_setup(self, ip, shutdown_hook):
+ last_in = {'nrspaces': 0}
+ for attr, prompt_class in (
+ ('prompt1', PowerlinePrompt1),
+ ('prompt2', PowerlinePrompt2),
+ ('prompt_out', PowerlinePromptOut)
+ ):
+ old_prompt = getattr(ip.IP.outputcache, attr)
+ prompt = prompt_class(self, last_in, old_prompt)
+ setattr(ip.IP.outputcache, attr, prompt)
+ ip.expose_magic('powerline', self.ipython_magic)
+ shutdown_hook.powerline = ref(self)
+
+
+class ShutdownHook(object):
+ powerline = lambda: None
+
+ def __call__(self):
+ from IPython.ipapi import TryNext
+ powerline = self.powerline()
+ if powerline is not None:
+ powerline.shutdown()
+ raise TryNext()
+
+
+def setup(**kwargs):
+ ip = get_ipython()
+
+ powerline = ConfigurableIPythonPowerline(**kwargs)
+ shutdown_hook = ShutdownHook()
+
+ def late_startup_hook():
+ powerline.setup(ip, shutdown_hook)
+ raise TryNext()
+
+ ip.IP.hooks.late_startup_hook.add(late_startup_hook)
+ ip.IP.hooks.shutdown_hook.add(shutdown_hook)
diff --git a/powerline/bindings/ipython/since_5.py b/powerline/bindings/ipython/since_5.py
new file mode 100644
index 0000000..5a899ae
--- /dev/null
+++ b/powerline/bindings/ipython/since_5.py
@@ -0,0 +1,81 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from weakref import ref
+
+from IPython.terminal.prompts import Prompts
+from pygments.token import Token # NOQA
+
+from powerline.ipython import IPythonPowerline
+from powerline.renderers.ipython.since_5 import PowerlinePromptStyle
+from powerline.bindings.ipython.post_0_11 import PowerlineMagics, ShutdownHook
+
+
+class ConfigurableIPythonPowerline(IPythonPowerline):
+ def init(self, ip):
+ config = ip.config.Powerline
+ self.config_overrides = config.get('config_overrides')
+ self.theme_overrides = config.get('theme_overrides', {})
+ self.config_paths = config.get('config_paths')
+ super(ConfigurableIPythonPowerline, self).init(
+ renderer_module='.since_5')
+
+ def do_setup(self, ip, prompts, shutdown_hook):
+ prompts.powerline = self
+
+ msfn_missing = ()
+ saved_msfn = getattr(ip, '_make_style_from_name', msfn_missing)
+
+ if hasattr(saved_msfn, 'powerline_original'):
+ saved_msfn = saved_msfn.powerline_original
+
+ def _make_style_from_name(ip, name):
+ prev_style = saved_msfn(name)
+ new_style = PowerlinePromptStyle(lambda: prev_style)
+ return new_style
+
+ _make_style_from_name.powerline_original = saved_msfn
+
+ if not isinstance(ip._style, PowerlinePromptStyle):
+ prev_style = ip._style
+ ip._style = PowerlinePromptStyle(lambda: prev_style)
+
+ if not isinstance(saved_msfn, type(self.init)):
+ _saved_msfn = saved_msfn
+ saved_msfn = lambda: _saved_msfn(ip)
+
+ if saved_msfn is not msfn_missing:
+ ip._make_style_from_name = _make_style_from_name
+
+ magics = PowerlineMagics(ip, self)
+ ip.register_magics(magics)
+
+ if shutdown_hook:
+ shutdown_hook.powerline = ref(self)
+
+
+class PowerlinePrompts(Prompts):
+ '''Class that returns powerline prompts
+ '''
+ def __init__(self, shell):
+ shutdown_hook = ShutdownHook(shell)
+ powerline = ConfigurableIPythonPowerline(shell)
+ self.shell = shell
+ powerline.do_setup(shell, self, shutdown_hook)
+ self.last_output_count = None
+ self.last_output = {}
+
+ for prompt in ('in', 'continuation', 'rewrite', 'out'):
+ exec((
+ 'def {0}_prompt_tokens(self, *args, **kwargs):\n'
+ ' if self.last_output_count != self.shell.execution_count:\n'
+ ' self.last_output.clear()\n'
+ ' self.last_output_count = self.shell.execution_count\n'
+ ' if "{0}" not in self.last_output:\n'
+ ' self.last_output["{0}"] = self.powerline.render('
+ ' side="left",'
+ ' matcher_info="{1}",'
+ ' segment_info=self.shell,'
+ ' ) + [(Token.Generic.Prompt, " ")]\n'
+ ' return self.last_output["{0}"]'
+ ).format(prompt, 'in2' if prompt == 'continuation' else prompt))
diff --git a/powerline/bindings/ipython/since_7.py b/powerline/bindings/ipython/since_7.py
new file mode 100644
index 0000000..4d35b68
--- /dev/null
+++ b/powerline/bindings/ipython/since_7.py
@@ -0,0 +1,78 @@
+# vim:fileencoding=utf-8:noet
+from weakref import ref
+from atexit import register as atexit
+
+from IPython.terminal.prompts import Prompts
+from pygments.token import Token # NOQA
+
+from powerline.ipython import IPythonPowerline
+from powerline.renderers.ipython.since_7 import PowerlinePromptStyle
+from powerline.bindings.ipython.post_0_11 import PowerlineMagics
+
+
+class ConfigurableIPythonPowerline(IPythonPowerline):
+ def init(self, ip):
+ config = ip.config.Powerline
+ self.config_overrides = config.get('config_overrides')
+ self.theme_overrides = config.get('theme_overrides', {})
+ self.config_paths = config.get('config_paths')
+ super(ConfigurableIPythonPowerline, self).init(
+ renderer_module='.since_7')
+
+ def do_setup(self, ip, prompts):
+ prompts.powerline = self
+
+ msfn_missing = ()
+ saved_msfn = getattr(ip, '_make_style_from_name', msfn_missing)
+
+ if hasattr(saved_msfn, 'powerline_original'):
+ saved_msfn = saved_msfn.powerline_original
+
+ def _make_style_from_name(ip, name):
+ prev_style = saved_msfn(name)
+ new_style = PowerlinePromptStyle(lambda: prev_style)
+ return new_style
+
+ _make_style_from_name.powerline_original = saved_msfn
+
+ if not isinstance(ip._style, PowerlinePromptStyle):
+ prev_style = ip._style
+ ip._style = PowerlinePromptStyle(lambda: prev_style)
+
+ if not isinstance(saved_msfn, type(self.init)):
+ _saved_msfn = saved_msfn
+ saved_msfn = lambda: _saved_msfn(ip)
+
+ if saved_msfn is not msfn_missing:
+ ip._make_style_from_name = _make_style_from_name
+
+ magics = PowerlineMagics(ip, self)
+ ip.register_magics(magics)
+
+ atexit(self.shutdown)
+
+
+class PowerlinePrompts(Prompts):
+ '''Class that returns powerline prompts
+ '''
+ def __init__(self, shell):
+ powerline = ConfigurableIPythonPowerline(shell)
+ self.shell = shell
+ powerline.do_setup(shell, self)
+ self.last_output_count = None
+ self.last_output = {}
+
+ for prompt in ('in', 'continuation', 'rewrite', 'out'):
+ exec((
+ 'def {0}_prompt_tokens(self, *args, **kwargs):\n'
+ ' if self.last_output_count != self.shell.execution_count:\n'
+ ' self.last_output.clear()\n'
+ ' self.last_output_count = self.shell.execution_count\n'
+ ' if "{0}" not in self.last_output:\n'
+ ' self.last_output["{0}"] = self.powerline.render('
+ ' side="left",'
+ ' matcher_info="{1}",'
+ ' segment_info=self.shell,'
+ ' ) + [(Token.Generic.Prompt, " ")]\n'
+ ' return self.last_output["{0}"]'
+ ).format(prompt, 'in2' if prompt == 'continuation' else prompt))
diff --git a/powerline/bindings/lemonbar/powerline-lemonbar.py b/powerline/bindings/lemonbar/powerline-lemonbar.py
new file mode 100755
index 0000000..9511f28
--- /dev/null
+++ b/powerline/bindings/lemonbar/powerline-lemonbar.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import time
+import re
+import subprocess
+
+from threading import Lock, Timer
+
+from powerline.lemonbar import LemonbarPowerline
+from powerline.commands.lemonbar import get_argparser
+from powerline.bindings.wm import get_connected_xrandr_outputs
+
+
+if __name__ == '__main__':
+ parser = get_argparser()
+ args = parser.parse_args()
+
+ powerline = LemonbarPowerline()
+ powerline.update_renderer()
+ bars = []
+
+ for screen in get_connected_xrandr_outputs(powerline.pl):
+ command = [args.bar_command, '-g', '{0}x{1}+{2}+{3}'.format(screen['width'], args.height, screen['x'], screen['y'])] + args.args[1:]
+ process = subprocess.Popen(command, stdin=subprocess.PIPE)
+ bars.append((screen['name'], process, int(screen['width']) / 5))
+
+ lock = Lock()
+ modes = ['default']
+
+ def render(reschedule=False):
+ if reschedule:
+ Timer(args.interval, render, kwargs={'reschedule': True}).start()
+
+ global lock
+ with lock:
+ for output, process, width in bars:
+ process.stdin.write(powerline.render(mode=modes[0], width=width, matcher_info=output).encode('utf-8') + b'\n')
+ process.stdin.flush()
+
+ def update(evt):
+ modes[0] = evt.change
+ render()
+
+ render(reschedule=True)
+
+ if args.i3:
+ try:
+ import i3ipc
+ except ImportError:
+ import i3
+ i3.Subscription(lambda evt, data, sub: render(), 'workspace')
+ else:
+ conn = i3ipc.Connection()
+ conn.on('workspace::focus', lambda conn, evt: render())
+ conn.on('mode', lambda conn, evt: update(evt))
+ conn.main()
+
+ while True:
+ time.sleep(1e8)
diff --git a/powerline/bindings/pdb/__init__.py b/powerline/bindings/pdb/__init__.py
new file mode 100644
index 0000000..4033e61
--- /dev/null
+++ b/powerline/bindings/pdb/__init__.py
@@ -0,0 +1,183 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import pdb
+
+from powerline.pdb import PDBPowerline
+from powerline.lib.encoding import get_preferred_output_encoding
+from powerline.lib.unicode import unicode
+
+
+if sys.version_info < (3,):
+ # XXX The below classes make code compatible with PDBpp which uses pyrepl
+ # which does not expect unicode or something above ASCII. They are
+ # completely not needed if pdbpp is not used, but that’s not always the
+ # case.
+ class PowerlineRenderBytesResult(bytes):
+ def __new__(cls, s, encoding=None):
+ encoding = encoding or s.encoding
+ if isinstance(s, PowerlineRenderResult):
+ return s.encode(encoding)
+ self = bytes.__new__(cls, s.encode(encoding) if isinstance(s, unicode) else s)
+ self.encoding = encoding
+ return self
+
+ for meth in (
+ '__contains__',
+ 'partition', 'rpartition',
+ 'split', 'rsplit',
+ 'count', 'join',
+ ):
+ exec((
+ 'def {0}(self, *args):\n'
+ ' if any((isinstance(arg, unicode) for arg in args)):\n'
+ ' return self.__unicode__().{0}(*args)\n'
+ ' else:\n'
+ ' return bytes.{0}(self, *args)'
+ ).format(meth))
+
+ for meth in (
+ 'find', 'rfind',
+ 'index', 'rindex',
+ ):
+ exec((
+ 'def {0}(self, *args):\n'
+ ' if any((isinstance(arg, unicode) for arg in args)):\n'
+ ' args = [arg.encode(self.encoding) if isinstance(arg, unicode) else arg for arg in args]\n'
+ ' return bytes.{0}(self, *args)'
+ ).format(meth))
+
+ def __len__(self):
+ return len(self.decode(self.encoding))
+
+ def __getitem__(self, *args):
+ return PowerlineRenderBytesResult(bytes.__getitem__(self, *args), encoding=self.encoding)
+
+ def __getslice__(self, *args):
+ return PowerlineRenderBytesResult(bytes.__getslice__(self, *args), encoding=self.encoding)
+
+ @staticmethod
+ def add(encoding, *args):
+ if any((isinstance(arg, unicode) for arg in args)):
+ return PowerlineRenderResult(''.join((
+ arg
+ if isinstance(arg, unicode)
+ else arg.decode(encoding)
+ for arg in args
+ )), encoding)
+ else:
+ return PowerlineRenderBytesResult(b''.join(args), encoding=encoding)
+
+ def __add__(self, other):
+ return self.add(self.encoding, self, other)
+
+ def __radd__(self, other):
+ return self.add(self.encoding, other, self)
+
+ def __unicode__(self):
+ return PowerlineRenderResult(self)
+
+ class PowerlineRenderResult(unicode):
+ def __new__(cls, s, encoding=None):
+ encoding = (
+ encoding
+ or getattr(s, 'encoding', None)
+ or get_preferred_output_encoding()
+ )
+ if isinstance(s, unicode):
+ self = unicode.__new__(cls, s)
+ else:
+ self = unicode.__new__(cls, s, encoding, 'replace')
+ self.encoding = encoding
+ return self
+
+ def __str__(self):
+ return PowerlineRenderBytesResult(self)
+
+ def __getitem__(self, *args):
+ return PowerlineRenderResult(unicode.__getitem__(self, *args))
+
+ def __getslice__(self, *args):
+ return PowerlineRenderResult(unicode.__getslice__(self, *args))
+
+ @staticmethod
+ def add(encoding, *args):
+ return PowerlineRenderResult(''.join((
+ arg
+ if isinstance(arg, unicode)
+ else arg.decode(encoding)
+ for arg in args
+ )), encoding)
+
+ def __add__(self, other):
+ return self.add(self.encoding, self, other)
+
+ def __radd__(self, other):
+ return self.add(self.encoding, other, self)
+
+ def encode(self, *args, **kwargs):
+ return PowerlineRenderBytesResult(unicode.encode(self, *args, **kwargs), args[0])
+else:
+ PowerlineRenderResult = str
+
+
+def use_powerline_prompt(cls):
+ '''Decorator that installs powerline prompt to the class
+
+ :param pdb.Pdb cls:
+ Class that should be decorated.
+
+ :return:
+ ``cls`` argument or a class derived from it. Latter is used to turn
+ old-style classes into new-style classes.
+ '''
+ @property
+ def prompt(self):
+ try:
+ powerline = self.powerline
+ except AttributeError:
+ powerline = PDBPowerline()
+ powerline.setup(self)
+ self.powerline = powerline
+ return PowerlineRenderResult(powerline.render(side='left'))
+
+ @prompt.setter
+ def prompt(self, _):
+ pass
+
+ @prompt.deleter
+ def prompt(self):
+ pass
+
+ if not hasattr(cls, '__class__'):
+ # Old-style class: make it new-style or @property will not work.
+ old_cls = cls
+
+ class cls(cls, object):
+ __module__ = cls.__module__
+ __doc__ = cls.__doc__
+
+ cls.__name__ = old_cls.__name__
+
+ cls.prompt = prompt
+
+ return cls
+
+
+def main():
+ '''Run module as a script
+
+ Uses :py:func:`pdb.main` function directly, but prior to that it mocks
+ :py:class:`pdb.Pdb` class with powerline-specific class instance.
+ '''
+ orig_pdb = pdb.Pdb
+
+ @use_powerline_prompt
+ class Pdb(pdb.Pdb, object):
+ def __init__(self):
+ orig_pdb.__init__(self)
+
+ pdb.Pdb = Pdb
+
+ return pdb.main()
diff --git a/powerline/bindings/pdb/__main__.py b/powerline/bindings/pdb/__main__.py
new file mode 100755
index 0000000..768b2f2
--- /dev/null
+++ b/powerline/bindings/pdb/__main__.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.bindings.pdb import main
+
+
+if __name__ == '__main__':
+ main()
diff --git a/powerline/bindings/qtile/__init__.py b/powerline/bindings/qtile/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/bindings/qtile/__init__.py
diff --git a/powerline/bindings/qtile/widget.py b/powerline/bindings/qtile/widget.py
new file mode 100644
index 0000000..92e3a27
--- /dev/null
+++ b/powerline/bindings/qtile/widget.py
@@ -0,0 +1,61 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from libqtile.bar import CALCULATED
+from libqtile.widget import TextBox
+
+from powerline import Powerline
+
+
+class QTilePowerline(Powerline):
+ def do_setup(self, obj):
+ obj.powerline = self
+
+
+class PowerlineTextBox(TextBox):
+ # TODO Replace timeout argument with update_interval argument in next major
+ # release.
+ def __init__(self, timeout=2, text=b' ', width=CALCULATED, side='right', update_interval=None, **config):
+ super(PowerlineTextBox, self).__init__(text, width, **config)
+ self.side = side
+ self.update_interval = update_interval or timeout
+ self.did_run_timer_setup = False
+ powerline = QTilePowerline(ext='wm', renderer_module='pango_markup')
+ powerline.setup(self)
+
+ def update(self):
+ if not self.configured:
+ return True
+ self.text = self.powerline.render(side=self.side).encode('utf-8')
+ self.bar.draw()
+ return True
+
+ def cmd_update(self, text):
+ self.update(text)
+
+ def cmd_get(self):
+ return self.text
+
+ def timer_setup(self):
+ if not self.did_run_timer_setup:
+ self.did_run_timer_setup = True
+ self.timeout_add(self.update_interval, self.update)
+
+ def _configure(self, qtile, bar):
+ super(PowerlineTextBox, self)._configure(qtile, bar)
+ if self.layout.markup:
+ # QTile-0.9.1: no need to recreate layout or run timer_setup
+ return
+ self.layout = self.drawer.textlayout(
+ self.text,
+ self.foreground,
+ self.font,
+ self.fontsize,
+ self.fontshadow,
+ markup=True,
+ )
+ self.timer_setup()
+
+
+# TODO: Remove this at next major release
+Powerline = PowerlineTextBox
diff --git a/powerline/bindings/rc/powerline.rc b/powerline/bindings/rc/powerline.rc
new file mode 100644
index 0000000..b2d6538
--- /dev/null
+++ b/powerline/bindings/rc/powerline.rc
@@ -0,0 +1,92 @@
+fn _powerline_sigwinch {
+ _POWERLINE_COLUMNS = `{
+ stty size | cut -d' ' -f2
+ }
+ _powerline_tmux_setenv COLUMNS $_POWERLINE_COLUMNS
+}
+fn _powerline_update_pwd {
+ _POWERLINE_NEW_PWD = `{pwd}
+ if (test $^_POWERLINE_NEW_PWD '=' $^_POWERLINE_SAVED_PWD) {
+ _POWERLINE_SAVED_PWD = $_POWERLINE_NEW_PWD
+ _powerline_tmux_setenv PWD $_POWERLINE_SAVED_PWD
+ }
+}
+fn _powerline_continuation_prompt {
+ _powerline_prompt --renderer-arg 'local_theme=continuation' $*
+}
+fn _powerline_prompt {
+ $POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS shell aboveleft -r.readline --last-pipe-status $^_POWERLINE_STATUS --last-exit-code $_POWERLINE_STATUS($#_POWERLINE_STATUS) --jobnum $_POWERLINE_JOBNUM --renderer-arg 'client_id='$pid $*
+}
+fn _powerline_set_prompt {
+ _POWERLINE_STATUS = ( $status )
+ _POWERLINE_JOBNUM = $#apids
+ prompt = (``() {
+ _powerline_prompt
+ } ``() {
+ _powerline_continuation_prompt
+ })
+ _powerline_update_pwd
+}
+
+fn _powerline_common_setup {
+ fn sigwinch {
+ _powerline_sigwinch
+ }
+ _powerline_sigwinch
+ _POWERLINE_SAVED_PWD = ''
+}
+
+fn _powerline_tmux_pane {
+ if (test -n $TMUX_PANE) {
+ echo $TMUX_PANE | tr -d ' %'
+ } else {
+ TMUX=$_POWERLINE_TMUX tmux display -p '#D' | tr -d ' %'
+ }
+}
+
+fn _powerline_tmux_setenv {
+}
+
+if (test -z $POWERLINE_CONFIG_COMMAND) {
+ if (which powerline-config >/dev/null) {
+ POWERLINE_CONFIG_COMMAND = powerline-config
+ } else {
+ echo powerline-config executable not found, unable to proceed >[2=1]
+ }
+}
+if (test -n $POWERLINE_CONFIG_COMMAND) {
+ if ($POWERLINE_CONFIG_COMMAND shell --shell rcsh uses prompt) {
+ if (test -n $POWERLINE_COMMAND_ARGS) {
+ # Perform splitting
+ POWERLINE_COMMAND_ARGS=( `{echo $POWERLINE_COMMAND_ARGS} )
+ }
+ fn prompt {
+ _powerline_set_prompt
+ }
+ if (test -z $POWERLINE_SHELL_CONTINUATION$POWERLINE_RCSH_CONTINUATION) {
+ _POWERLINE_STATUS = 0
+ _POWERLINE_JOBNUM = 0
+ _POWERLINE_CONTINUATION = `{
+ _powerline_continuation_prompt
+ }
+ fn _powerline_continuation_prompt {
+ echo -n $_POWERLINE_CONTINUATION
+ }
+ }
+ _powerline_common_setup
+ }
+ if (test -n $TMUX) {
+ if ($POWERLINE_CONFIG_COMMAND shell --shell rcsh uses tmux) {
+ _POWERLINE_TMUX=$TMUX
+ fn _powerline_tmux_setenv {
+ if (test -n $2) {
+ TMUX=$_POWERLINE_TMUX tmux setenv -g TMUX_$1^_`{
+ _powerline_tmux_pane
+ } $2
+ }
+ }
+ _powerline_common_setup
+ }
+ }
+}
+# vim: ft=rcshell
diff --git a/powerline/bindings/shell/powerline.sh b/powerline/bindings/shell/powerline.sh
new file mode 100644
index 0000000..e1b067d
--- /dev/null
+++ b/powerline/bindings/shell/powerline.sh
@@ -0,0 +1,239 @@
+_POWERLINE_SOURCED="$_"
+_powerline_columns_fallback() {
+ if command -v stty >/dev/null ; then
+ # Ksh does not have “local” built-in
+ _powerline_cols="$(stty size 2>/dev/null)"
+ if ! test -z "$_powerline_cols" ; then
+ echo "${_powerline_cols#* }"
+ return 0
+ fi
+ fi
+ echo 0
+ return 0
+}
+
+_powerline_has_jobs_in_subshell() {
+ if test -n "$_POWERLINE_HAS_JOBS_IN_SUBSHELL" ; then
+ return $_POWERLINE_HAS_JOBS_IN_SUBSHELL
+ elif test -z "$1" ; then
+ sleep 1 &
+ # Check whether shell outputs anything in a subshell when using jobs
+ # built-in. Shells like dash will not output anything meaning that
+ # I have to bother with temporary files.
+ test "$(jobs -p|wc -l)" -gt 0
+ else
+ case "$1" in
+ dash|bb|ash) return 1 ;;
+ mksh|ksh|bash) return 0 ;;
+ *) _powerline_has_jobs_in_subshell ;;
+ esac
+ fi
+ _POWERLINE_HAS_JOBS_IN_SUBSHELL=$?
+ return $_POWERLINE_HAS_JOBS_IN_SUBSHELL
+}
+
+_powerline_set_append_trap() {
+ if _powerline_has_jobs_in_subshell "$@" ; then
+ _powerline_append_trap() {
+ # Arguments: command, signal
+ # Ksh does not have “local” built-in
+ _powerline_traps="$(trap)"
+ if echo "$_powerline_traps" | grep -cm1 $2'$' >/dev/null ; then
+ _powerline_traps="$(echo "$_powerline_traps" | sed "s/ $2/'\\n$1' $2/")"
+ eval "$_powerline_traps"
+ else
+ trap "$1" $2
+ fi
+ }
+ else
+ _powerline_append_trap() {
+ # Arguments: command, signal
+ _powerline_create_temp
+ trap > $_POWERLINE_TEMP
+ if grep -cm1 $2'$' $_POWERLINE_TEMP >/dev/null ; then
+ sed -i -e "s/ $2/'\\n$1' $2/"
+ . $_POWERLINE_TEMP
+ else
+ trap "$1" $2
+ fi
+ echo -n > $_POWERLINE_TEMP
+ }
+ fi
+ _powerline_set_append_trap() {
+ return 0
+ }
+}
+
+_powerline_create_temp() {
+ if test -z "$_POWERLINE_TEMP" || ! test -e "$_POWERLINE_TEMP" ; then
+ _POWERLINE_TEMP="$(mktemp "${TMPDIR:-/tmp}/powerline.XXXXXXXX")"
+ _powerline_append_trap 'rm $_POWERLINE_TEMP' EXIT
+ fi
+}
+
+_powerline_set_set_jobs() {
+ if _powerline_has_jobs_in_subshell "$@" ; then
+ _powerline_set_jobs() {
+ _POWERLINE_JOBS="$(jobs -p|wc -l|tr -d ' ')"
+ }
+ else
+ _powerline_set_append_trap "$@"
+ _POWERLINE_PID=$$
+ _powerline_append_trap '_powerline_do_set_jobs' USR1
+ _powerline_do_set_jobs() {
+ _powerline_create_temp
+ jobs -p > $_POWERLINE_TEMP
+ }
+ # This command will always be launched from a subshell, thus a hack is
+ # needed to run `jobs -p` outside of the subshell.
+ _powerline_set_jobs() {
+ kill -USR1 $_POWERLINE_PID
+ # Note: most likely this will read data from the previous run. Tests
+ # show that it is OK for some reasons.
+ _POWERLINE_JOBS="$(wc -l < $_POWERLINE_TEMP | tr -d ' ')"
+ }
+ fi
+ _powerline_set_set_jobs() {
+ return 0
+ }
+}
+
+_powerline_set_command() {
+ if test -z "${POWERLINE_COMMAND}" ; then
+ POWERLINE_COMMAND="$("$POWERLINE_CONFIG_COMMAND" shell command)"
+ fi
+}
+
+_powerline_tmux_pane() {
+ echo "${TMUX_PANE:-`TMUX="$_POWERLINE_TMUX" tmux display -p "#D"`}" | \
+ tr -d ' %'
+}
+
+_powerline_tmux_setenv() {
+ TMUX="$_POWERLINE_TMUX" tmux setenv -g TMUX_"$1"_`_powerline_tmux_pane` "$2"
+ TMUX="$_POWERLINE_TMUX" tmux refresh -S
+}
+
+_powerline_tmux_set_pwd() {
+ if test "$_POWERLINE_SAVED_PWD" != "$PWD" ; then
+ _POWERLINE_SAVED_PWD="$PWD"
+ _powerline_tmux_setenv PWD "$PWD"
+ fi
+}
+
+_powerline_tmux_set_columns() {
+ _powerline_tmux_setenv COLUMNS "${COLUMNS:-$(_powerline_columns_fallback)}"
+}
+
+_powerline_set_renderer_arg() {
+ case "$1" in
+ bb|ash) _POWERLINE_RENDERER_ARG="-r .bash" ;;
+ mksh|ksh) _POWERLINE_RENDERER_ARG="-r .ksh" ;;
+ bash|dash) _POWERLINE_RENDERER_ARG= ;;
+ esac
+}
+
+_powerline_set_jobs() {
+ _powerline_set_set_jobs
+ _powerline_set_jobs
+}
+
+_powerline_local_prompt() {
+ # Arguments: side, exit_code, local theme
+ _powerline_set_jobs
+ "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \
+ $_POWERLINE_RENDERER_ARG \
+ --renderer-arg="client_id=$$" \
+ --last-exit-code=$2 \
+ --jobnum=$_POWERLINE_JOBS \
+ --renderer-arg="local_theme=$3"
+}
+
+_powerline_prompt() {
+ # Arguments: side, exit_code
+ _powerline_set_jobs
+ "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS shell $1 \
+ --width="${COLUMNS:-$(_powerline_columns_fallback)}" \
+ $_POWERLINE_RENDERER_ARG \
+ --renderer-arg="client_id=$$" \
+ --last-exit-code=$2 \
+ --jobnum=$_POWERLINE_JOBS
+ _powerline_update_psN
+}
+
+_powerline_setup_psN() {
+ case "$1" in
+ mksh|ksh|bash)
+ _POWERLINE_PID=$$
+ _powerline_update_psN() {
+ kill -USR1 $_POWERLINE_PID
+ }
+ # No command substitution in PS2 and PS3
+ _powerline_set_psN() {
+ if test -n "$POWERLINE_SHELL_CONTINUATION" ; then
+ PS2="$(_powerline_local_prompt left $? continuation)"
+ fi
+ if test -n "$POWERLINE_SHELL_SELECT" ; then
+ PS3="$(_powerline_local_prompt left $? select)"
+ fi
+ }
+ _powerline_append_trap '_powerline_set_psN' USR1
+ _powerline_set_psN
+ ;;
+ bb|ash|dash)
+ _powerline_update_psN() {
+ # Do nothing
+ return
+ }
+ PS2='$(_powerline_local_prompt left $? continuation)'
+ # No select support
+ ;;
+ esac
+}
+
+_powerline_setup_prompt() {
+ VIRTUAL_ENV_DISABLE_PROMPT=1
+ _powerline_set_append_trap "$@"
+ _powerline_set_set_jobs "$@"
+ _powerline_set_command "$@"
+ _powerline_set_renderer_arg "$@"
+ PS1='$(_powerline_prompt aboveleft $?)'
+ PS2="$(_powerline_local_prompt left 0 continuation)"
+ PS3="$(_powerline_local_prompt left 0 select)"
+ _powerline_setup_psN "$@"
+}
+
+_powerline_init_tmux_support() {
+ # Dash does not have &>/dev/null
+ if test -n "$TMUX" && tmux refresh -S >/dev/null 2>/dev/null ; then
+ # TMUX variable may be unset to create new tmux session inside this one
+ _POWERLINE_TMUX="$TMUX"
+
+ _powerline_set_append_trap "$@"
+
+ # If _powerline_tmux_set_pwd is used before _powerline_prompt it sets $?
+ # to zero in ksh.
+ PS1="$PS1"'$(_powerline_tmux_set_pwd)'
+ _powerline_append_trap '_powerline_tmux_set_columns' WINCH
+ _powerline_tmux_set_columns
+ fi
+}
+
+if test -z "${POWERLINE_CONFIG_COMMAND}" ; then
+ if command -v powerline-config >/dev/null ; then
+ POWERLINE_CONFIG_COMMAND=powerline-config
+ else
+ POWERLINE_CONFIG_COMMAND="$(dirname "$_POWERLINE_SOURCED")/../../../scripts/powerline-config"
+ fi
+fi
+
+# Strips the leading `-`: it may be present when shell is a login shell
+_POWERLINE_USED_SHELL=${0#-}
+_POWERLINE_USED_SHELL=${_POWERLINE_USED_SHELL##*/}
+
+if "${POWERLINE_CONFIG_COMMAND}" shell uses tmux ; then
+ _powerline_init_tmux_support $_POWERLINE_USED_SHELL
+fi
+if "${POWERLINE_CONFIG_COMMAND}" shell --shell=bash uses prompt ; then
+ _powerline_setup_prompt $_POWERLINE_USED_SHELL
+fi
diff --git a/powerline/bindings/tcsh/powerline.tcsh b/powerline/bindings/tcsh/powerline.tcsh
new file mode 100644
index 0000000..4897b4c
--- /dev/null
+++ b/powerline/bindings/tcsh/powerline.tcsh
@@ -0,0 +1,60 @@
+# http://unix.stackexchange.com/questions/4650/determining-path-to-sourced-shell-script:
+# > In tcsh, $_ at the beginning of the script will contain the location if the
+# > file was sourced and $0 contains it if it was run.
+#
+# Guess this relies on `$_` being set as to last argument to previous command
+# which must be `.` or `source` in this case
+set POWERLINE_SOURCED=($_)
+if ! $?POWERLINE_CONFIG_COMMAND then
+ if ( { which powerline-config > /dev/null } ) then
+ set POWERLINE_CONFIG_COMMAND="powerline-config"
+ else
+ set POWERLINE_CONFIG_COMMAND="$POWERLINE_SOURCED[2]:h:h:h:h/scripts/powerline-config"
+ endif
+else
+ if "$POWERLINE_CONFIG_COMMAND" == "" then
+ if ( { which powerline-config > /dev/null } ) then
+ set POWERLINE_CONFIG_COMMAND="powerline-config"
+ else
+ set POWERLINE_CONFIG_COMMAND="$POWERLINE_SOURCED[2]:h:h:h:h/scripts/powerline-config"
+ endif
+ endif
+endif
+if ( { $POWERLINE_CONFIG_COMMAND shell --shell=tcsh uses tmux } ) then
+ if ( $?TMUX_PANE ) then
+ if ( "$TMUX_PANE" == "" ) then
+ set _POWERLINE_TMUX_PANE="`tmux display -p '#D'`"
+ else
+ set _POWERLINE_TMUX_PANE="$TMUX_PANE"
+ endif
+ else
+ set _POWERLINE_TMUX_PANE="`tmux display -p '#D'`"
+ endif
+ set _POWERLINE_TMUX_PANE="`echo $_POWERLINE_TMUX_PANE:q | tr -d '% '`"
+ alias _powerline_tmux_set_pwd 'if ( $?TMUX && { tmux refresh -S >&/dev/null } ) tmux setenv -g TMUX_PWD_$_POWERLINE_TMUX_PANE $PWD:q ; if ( $?TMUX ) tmux refresh -S >&/dev/null'
+ alias cwdcmd "`alias cwdcmd` ; _powerline_tmux_set_pwd"
+endif
+if ( { $POWERLINE_CONFIG_COMMAND shell --shell=tcsh uses prompt } ) then
+ if ! $?POWERLINE_COMMAND then
+ set POWERLINE_COMMAND="`$POWERLINE_CONFIG_COMMAND:q shell command`"
+ else
+ if "$POWERLINE_COMMAND" == "" then
+ set POWERLINE_COMMAND="`$POWERLINE_CONFIG_COMMAND:q shell command`"
+ endif
+ endif
+ if ! $?POWERLINE_COMMAND_ARGS then
+ set POWERLINE_COMMAND_ARGS=""
+ endif
+
+ if ( $?POWERLINE_NO_TCSH_ABOVE || $?POWERLINE_NO_SHELL_ABOVE ) then
+ alias _powerline_above true
+ else
+ alias _powerline_above '$POWERLINE_COMMAND:q $POWERLINE_COMMAND_ARGS shell above --renderer-arg=client_id=$$ --last-exit-code=$POWERLINE_STATUS --width=$POWERLINE_COLUMNS'
+ endif
+
+ alias _powerline_set_prompt 'set prompt="`$POWERLINE_COMMAND:q $POWERLINE_COMMAND_ARGS shell left -r .tcsh --renderer-arg=client_id=$$ --last-exit-code=$POWERLINE_STATUS --width=$POWERLINE_COLUMNS`"'
+ alias _powerline_set_rprompt 'set rprompt="`$POWERLINE_COMMAND:q $POWERLINE_COMMAND_ARGS shell right -r .tcsh --renderer-arg=client_id=$$ --last-exit-code=$POWERLINE_STATUS --width=$POWERLINE_COLUMNS`"'
+ alias _powerline_set_columns 'set POWERLINE_COLUMNS=`stty size|cut -d" " -f2` ; set POWERLINE_COLUMNS=`expr $POWERLINE_COLUMNS - 2`'
+
+ alias precmd 'set POWERLINE_STATUS=$? ; '"`alias precmd`"' ; _powerline_set_columns ; _powerline_above ; _powerline_set_prompt ; _powerline_set_rprompt'
+endif
diff --git a/powerline/bindings/tmux/__init__.py b/powerline/bindings/tmux/__init__.py
new file mode 100644
index 0000000..eb84e7a
--- /dev/null
+++ b/powerline/bindings/tmux/__init__.py
@@ -0,0 +1,85 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import re
+import os
+import subprocess
+
+from collections import namedtuple
+
+from powerline.lib.shell import run_cmd
+
+
+TmuxVersionInfo = namedtuple('TmuxVersionInfo', ('major', 'minor', 'suffix'))
+
+
+def get_tmux_executable_name():
+ '''Returns tmux executable name
+
+ It should be defined in POWERLINE_TMUX_EXE environment variable, otherwise
+ it is simply “tmux”.
+ '''
+
+ return os.environ.get('POWERLINE_TMUX_EXE', 'tmux')
+
+
+def _run_tmux(runner, args):
+ return runner([get_tmux_executable_name()] + list(args))
+
+
+def run_tmux_command(*args):
+ '''Run tmux command, ignoring the output'''
+ _run_tmux(subprocess.check_call, args)
+
+
+def get_tmux_output(pl, *args):
+ '''Run tmux command and return its output'''
+ return _run_tmux(lambda cmd: run_cmd(pl, cmd), args)
+
+
+def set_tmux_environment(varname, value, remove=True):
+ '''Set tmux global environment variable
+
+ :param str varname:
+ Name of the variable to set.
+ :param str value:
+ Variable value.
+ :param bool remove:
+ True if variable should be removed from the environment prior to
+ attaching any client (runs ``tmux set-environment -r {varname}``).
+ '''
+ run_tmux_command('set-environment', '-g', varname, value)
+ if remove:
+ try:
+ run_tmux_command('set-environment', '-r', varname)
+ except subprocess.CalledProcessError:
+ # On tmux-2.0 this command may fail for whatever reason. Since it is
+ # critical just ignore the failure.
+ pass
+
+
+def source_tmux_file(fname):
+ '''Source tmux configuration file
+
+ :param str fname:
+ Full path to the sourced file.
+ '''
+ run_tmux_command('source', fname)
+
+
+NON_DIGITS = re.compile('[^0-9]+')
+DIGITS = re.compile('[0-9]+')
+NON_LETTERS = re.compile('[^a-z]+')
+
+
+def get_tmux_version(pl):
+ version_string = get_tmux_output(pl, '-V')
+ _, version_string = version_string.split(' ')
+ version_string = version_string.strip()
+ if version_string == 'master':
+ return TmuxVersionInfo(float('inf'), 0, version_string)
+ major, minor = version_string.split('.')
+ major = NON_DIGITS.subn('', major)[0]
+ suffix = DIGITS.subn('', minor)[0] or None
+ minor = NON_DIGITS.subn('', minor)[0]
+ return TmuxVersionInfo(int(major), int(minor), suffix)
diff --git a/powerline/bindings/tmux/powerline-base.conf b/powerline/bindings/tmux/powerline-base.conf
new file mode 100644
index 0000000..50a1079
--- /dev/null
+++ b/powerline/bindings/tmux/powerline-base.conf
@@ -0,0 +1,11 @@
+set -g status on
+set -g status-interval 2
+set -g status-left-length 20
+set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id=#{pane_id})'
+set -g status-right-length 150
+set -g window-status-format "#[$_POWERLINE_WINDOW_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER_SPACES#I#F #[$_POWERLINE_WINDOW_DIVIDER_COLOR]$_POWERLINE_LEFT_SOFT_DIVIDER#[default]#W $_POWERLINE_LEFT_HARD_DIVIDER_SPACES"
+set -g window-status-current-format "#[$_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER#[$_POWERLINE_WINDOW_CURRENT_COLOR]#I#F $_POWERLINE_LEFT_SOFT_DIVIDER#[$_POWERLINE_WINDOW_NAME_COLOR]#W #[$_POWERLINE_WINDOW_CURRENT_HARD_DIVIDER_NEXT_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER"
+
+# Legacy status-left definition to be overwritten for tmux Versions 1.8+
+set -g status-left "#[$_POWERLINE_SESSION_COLOR] #S #[$_POWERLINE_SESSION_HARD_DIVIDER_NEXT_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER#(env \"\$POWERLINE_COMMAND\" tmux left -R pane_id=#{pane_id})"
+# vim: ft=tmux
diff --git a/powerline/bindings/tmux/powerline.conf b/powerline/bindings/tmux/powerline.conf
new file mode 100644
index 0000000..29ec6a4
--- /dev/null
+++ b/powerline/bindings/tmux/powerline.conf
@@ -0,0 +1,2 @@
+if-shell 'env "$POWERLINE_CONFIG_COMMAND" tmux setup' '' 'run-shell "powerline-config tmux setup"'
+# vim: ft=tmux
diff --git a/powerline/bindings/tmux/powerline_tmux_1.7_plus.conf b/powerline/bindings/tmux/powerline_tmux_1.7_plus.conf
new file mode 100644
index 0000000..ab7d0b4
--- /dev/null
+++ b/powerline/bindings/tmux/powerline_tmux_1.7_plus.conf
@@ -0,0 +1,3 @@
+set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id=#{pane_id} --width=#{client_width} -R width_adjust=#{status-left-length})'
+set -g status-left "#[$_POWERLINE_SESSION_COLOR] #S #[$_POWERLINE_SESSION_HARD_DIVIDER_NEXT_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER#(env \"\$POWERLINE_COMMAND\" tmux left --width=#{client_width} -R width_adjust=#{status-right-length} -R pane_id=#{pane_id})"
+# vim: ft=tmux
diff --git a/powerline/bindings/tmux/powerline_tmux_1.8.conf b/powerline/bindings/tmux/powerline_tmux_1.8.conf
new file mode 100644
index 0000000..fbcd2a5
--- /dev/null
+++ b/powerline/bindings/tmux/powerline_tmux_1.8.conf
@@ -0,0 +1,5 @@
+# powerline_tmux_1.8.conf
+# tmux Version 1.8 introduces window-status-last-{attr,bg,fg}, which is
+# deprecated for versions 1.9+, thus only applicable to version 1.8.
+set -qg window-status-last-fg "$_POWERLINE_ACTIVE_WINDOW_FG"
+# vim: ft=tmux
diff --git a/powerline/bindings/tmux/powerline_tmux_1.8_minus.conf b/powerline/bindings/tmux/powerline_tmux_1.8_minus.conf
new file mode 100644
index 0000000..284eee0
--- /dev/null
+++ b/powerline/bindings/tmux/powerline_tmux_1.8_minus.conf
@@ -0,0 +1,11 @@
+# powerline_tmux_legacy_common.conf
+# tmux Version 1.8 and earlier (legacy) common options. The foo-{attr,bg,fg}
+# options are deprecated starting with tmux Version 1.9.
+set -g status-fg "$_POWERLINE_BACKGROUND_FG"
+set -g status-bg "$_POWERLINE_BACKGROUND_BG"
+set-window-option -g window-status-fg "$_POWERLINE_WINDOW_STATUS_FG"
+set-window-option -g window-status-activity-attr "$_POWERLINE_ACTIVITY_STATUS_ATTR_LEGACY"
+set-window-option -g window-status-bell-attr "$_POWERLINE_BELL_STATUS_ATTR_LEGACY"
+set-window-option -g window-status-activity-fg "$_POWERLINE_ACTIVITY_STATUS_FG"
+set-window-option -g window-status-bell-fg "$_POWERLINE_BELL_STATUS_FG"
+# vim: ft=tmux
diff --git a/powerline/bindings/tmux/powerline_tmux_1.8_plus.conf b/powerline/bindings/tmux/powerline_tmux_1.8_plus.conf
new file mode 100644
index 0000000..e7144fb
--- /dev/null
+++ b/powerline/bindings/tmux/powerline_tmux_1.8_plus.conf
@@ -0,0 +1,5 @@
+# powerline_tmux_1.8_plus.conf
+# tmux Version 1.8 introduces the 'client_prefix' format variable, applicable
+# for versions 1.8+
+set -qg status-left "#{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_FG]#[bg=$_POWERLINE_SESSION_PREFIX_BG]#[$_POWERLINE_SESSION_PREFIX_ATTR],#[fg=$_POWERLINE_SESSION_FG]#[bg=$_POWERLINE_SESSION_BG]#[$_POWERLINE_SESSION_ATTR]} #S #{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_BG],#[fg=$_POWERLINE_SESSION_BG]}#[bg=$_POWERLINE_BACKGROUND_BG]#[nobold]$_POWERLINE_LEFT_HARD_DIVIDER#(env \$POWERLINE_COMMAND \$POWERLINE_COMMAND_ARGS tmux left --width=#{client_width} -R width_adjust=#{status-right-length} -R pane_id=#{pane_id})"
+# vim: ft=tmux
diff --git a/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf b/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf
new file mode 100644
index 0000000..b1afaf4
--- /dev/null
+++ b/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf
@@ -0,0 +1,9 @@
+# powerline_tmux_1.9_plus.conf
+# Version 1.9 introduces the foo-style options, applicable to version 1.9+
+set-option -qg status-style "$_POWERLINE_BACKGROUND_COLOR"
+set-option -qg window-status-last-style "$_POWERLINE_ACTIVE_WINDOW_STATUS_COLOR"
+set-window-option -qg window-status-style "$_POWERLINE_WINDOW_STATUS_COLOR"
+set-window-option -qg window-status-activity-style "$_POWERLINE_ACTIVITY_STATUS_COLOR"
+set-window-option -qg window-status-bell-style "$_POWERLINE_BELL_STATUS_COLOR"
+set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right --width=#{client_width} -R width_adjust=#{status-left-length} -R pane_id=#{pane_id} -R pane_current_path=#{q:pane_current_path})'
+# vim: ft=tmux
diff --git a/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf
new file mode 100644
index 0000000..16703b7
--- /dev/null
+++ b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf
@@ -0,0 +1,3 @@
+# Starting from tmux-2.1 escaping of dollar signs inside #() is harmful
+set -qg status-left "#{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_FG]#[bg=$_POWERLINE_SESSION_PREFIX_BG]#[$_POWERLINE_SESSION_PREFIX_ATTR],#[fg=$_POWERLINE_SESSION_FG]#[bg=$_POWERLINE_SESSION_BG]#[$_POWERLINE_SESSION_ATTR]} #S #{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_BG],#[fg=$_POWERLINE_SESSION_BG]}#[bg=$_POWERLINE_BACKGROUND_BG]#[nobold]$_POWERLINE_LEFT_HARD_DIVIDER#(env $POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS tmux left --width=#{client_width} -R width_adjust=#{status-right-length} -R pane_id=#{pane_id} -R pane_current_path=#{q:pane_current_path})"
+set -g window-status-format "#[$_POWERLINE_WINDOW_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER_SPACES#I#{?window_flags,#F, } #[$_POWERLINE_WINDOW_DIVIDER_COLOR]$_POWERLINE_LEFT_SOFT_DIVIDER#[default]#W $_POWERLINE_LEFT_HARD_DIVIDER_SPACES"
diff --git a/powerline/bindings/vim/__init__.py b/powerline/bindings/vim/__init__.py
new file mode 100644
index 0000000..b754c1f
--- /dev/null
+++ b/powerline/bindings/vim/__init__.py
@@ -0,0 +1,482 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import codecs
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.lib.unicode import unicode
+
+
+if (
+ hasattr(vim, 'options')
+ and hasattr(vim, 'vvars')
+ and vim.vvars['version'] > 703
+):
+ if sys.version_info < (3,):
+ def get_vim_encoding():
+ return vim.options['encoding'] or 'ascii'
+ else:
+ def get_vim_encoding():
+ return vim.options['encoding'].decode('ascii') or 'ascii'
+elif hasattr(vim, 'eval'):
+ def get_vim_encoding():
+ return vim.eval('&encoding') or 'ascii'
+else:
+ def get_vim_encoding():
+ return 'utf-8'
+
+get_vim_encoding.__doc__ = (
+ '''Get encoding used for Vim strings
+
+ :return:
+ Value of ``&encoding``. If it is empty (i.e. Vim is compiled
+ without +multibyte) returns ``'ascii'``. When building documentation
+ outputs ``'utf-8'`` unconditionally.
+ '''
+)
+
+
+vim_encoding = get_vim_encoding()
+
+
+python_to_vim_types = {
+ unicode: (
+ lambda o: b'\'' + (o.translate({
+ ord('\''): '\'\'',
+ }).encode(vim_encoding)) + b'\''
+ ),
+ list: (
+ lambda o: b'[' + (
+ b','.join((python_to_vim(i) for i in o))
+ ) + b']'
+ ),
+ bytes: (lambda o: b'\'' + o.replace(b'\'', b'\'\'') + b'\''),
+ int: (str if str is bytes else (lambda o: unicode(o).encode('ascii'))),
+}
+python_to_vim_types[float] = python_to_vim_types[int]
+
+
+def python_to_vim(o):
+ return python_to_vim_types[type(o)](o)
+
+
+if sys.version_info < (3,):
+ def str_to_bytes(s):
+ return s
+
+ def unicode_eval(expr):
+ ret = vim.eval(expr)
+ return ret.decode(vim_encoding, 'powerline_vim_strtrans_error')
+else:
+ def str_to_bytes(s):
+ return s.encode(vim_encoding)
+
+ def unicode_eval(expr):
+ return vim.eval(expr)
+
+
+def safe_bytes_eval(expr):
+ return bytes(bytearray((
+ int(chunk) for chunk in (
+ vim.eval(
+ b'substitute(' + expr + b', ' +
+ b'\'^.*$\', \'\\=join(map(range(len(submatch(0))), ' +
+ b'"char2nr(submatch(0)[v:val])"))\', "")'
+ ).split()
+ )
+ )))
+
+
+def eval_bytes(expr):
+ try:
+ return str_to_bytes(vim.eval(expr))
+ except UnicodeDecodeError:
+ return safe_bytes_eval(expr)
+
+
+def eval_unicode(expr):
+ try:
+ return unicode_eval(expr)
+ except UnicodeDecodeError:
+ return safe_bytes_eval(expr).decode(vim_encoding, 'powerline_vim_strtrans_error')
+
+
+if hasattr(vim, 'bindeval'):
+ rettype_func = {
+ None: lambda f: f,
+ 'unicode': (
+ lambda f: (
+ lambda *args, **kwargs: (
+ f(*args, **kwargs).decode(
+ vim_encoding, 'powerline_vim_strtrans_error'
+ ))))
+ }
+ rettype_func['int'] = rettype_func['bytes'] = rettype_func[None]
+ rettype_func['str'] = rettype_func['bytes'] if str is bytes else rettype_func['unicode']
+
+ def vim_get_func(f, rettype=None):
+ '''Return a vim function binding.'''
+ try:
+ func = vim.bindeval('function("' + f + '")')
+ except vim.error:
+ return None
+ else:
+ return rettype_func[rettype](func)
+else:
+ rettype_eval = {
+ None: getattr(vim, 'eval', None),
+ 'int': lambda expr: int(vim.eval(expr)),
+ 'bytes': eval_bytes,
+ 'unicode': eval_unicode,
+ }
+ rettype_eval['str'] = rettype_eval[None]
+
+ class VimFunc(object):
+ '''Evaluate a vim function using vim.eval().
+
+ This is a fallback class for older vim versions.
+ '''
+ __slots__ = ('f', 'eval')
+
+ def __init__(self, f, rettype=None):
+ self.f = f.encode('utf-8')
+ self.eval = rettype_eval[rettype]
+
+ def __call__(self, *args):
+ return self.eval(self.f + b'(' + (b','.join((
+ python_to_vim(o) for o in args
+ ))) + b')')
+
+ vim_get_func = VimFunc
+
+
+def vim_get_autoload_func(f, rettype=None):
+ func = vim_get_func(f)
+ if not func:
+ vim.command('runtime! ' + f.replace('#', '/')[:f.rindex('#')] + '.vim')
+ func = vim_get_func(f)
+ return func
+
+
+if hasattr(vim, 'Function'):
+ def vim_func_exists(f):
+ try:
+ vim.Function(f)
+ except ValueError:
+ return False
+ else:
+ return True
+else:
+ def vim_func_exists(f):
+ try:
+ return bool(int(vim.eval('exists("*{0}")'.format(f))))
+ except vim.error:
+ return False
+
+
+if type(vim) is object:
+ vim_get_func = lambda *args, **kwargs: None
+
+
+_getbufvar = vim_get_func('getbufvar')
+_vim_exists = vim_get_func('exists', rettype='int')
+
+
+# It may crash on some old vim versions and I do not remember in which patch
+# I fixed this crash.
+if hasattr(vim, 'vvars') and vim.vvars[str('version')] > 703:
+ _vim_to_python_types = {
+ getattr(vim, 'Dictionary', None) or type(vim.bindeval('{}')):
+ lambda value: dict((
+ (_vim_to_python(k), _vim_to_python(v))
+ for k, v in value.items()
+ )),
+ getattr(vim, 'List', None) or type(vim.bindeval('[]')):
+ lambda value: [_vim_to_python(item) for item in value],
+ getattr(vim, 'Function', None) or type(vim.bindeval('function("mode")')):
+ lambda _: None,
+ }
+
+ def vim_getvar(varname):
+ return _vim_to_python(vim.vars[str(varname)])
+
+ def bufvar_exists(buffer, varname):
+ buffer = buffer or vim.current.buffer
+ return varname in buffer.vars
+
+ def vim_getwinvar(segment_info, varname):
+ return _vim_to_python(segment_info['window'].vars[str(varname)])
+
+ def vim_global_exists(name):
+ try:
+ vim.vars[name]
+ except KeyError:
+ return False
+ else:
+ return True
+else:
+ _vim_to_python_types = {
+ dict: (lambda value: dict(((k, _vim_to_python(v)) for k, v in value.items()))),
+ list: (lambda value: [_vim_to_python(i) for i in value]),
+ }
+
+ def vim_getvar(varname):
+ varname = 'g:' + varname
+ if _vim_exists(varname):
+ return vim.eval(varname)
+ else:
+ raise KeyError(varname)
+
+ def bufvar_exists(buffer, varname):
+ if not buffer or buffer.number == vim.current.buffer.number:
+ return int(vim.eval('exists("b:{0}")'.format(varname)))
+ else:
+ return int(vim.eval(
+ 'has_key(getbufvar({0}, ""), {1})'.format(buffer.number, varname)
+ ))
+
+ def vim_getwinvar(segment_info, varname):
+ result = vim.eval('getwinvar({0}, "{1}")'.format(segment_info['winnr'], varname))
+ if result == '':
+ if not int(vim.eval('has_key(getwinvar({0}, ""), "{1}")'.format(segment_info['winnr'], varname))):
+ raise KeyError(varname)
+ return result
+
+ def vim_global_exists(name):
+ return int(vim.eval('exists("g:' + name + '")'))
+
+
+def vim_command_exists(name):
+ return _vim_exists(':' + name)
+
+
+if sys.version_info < (3,):
+ getbufvar = _getbufvar
+else:
+ _vim_to_python_types[bytes] = lambda value: value.decode(vim_encoding)
+
+ def getbufvar(*args):
+ return _vim_to_python(_getbufvar(*args))
+
+
+_id = lambda value: value
+
+
+def _vim_to_python(value):
+ return _vim_to_python_types.get(type(value), _id)(value)
+
+
+if hasattr(vim, 'options'):
+ def vim_getbufoption(info, option):
+ return _vim_to_python(info['buffer'].options[str(option)])
+
+ def vim_getoption(option):
+ return vim.options[str(option)]
+
+ def vim_setoption(option, value):
+ vim.options[str(option)] = value
+else:
+ def vim_getbufoption(info, option):
+ return getbufvar(info['bufnr'], '&' + option)
+
+ def vim_getoption(option):
+ return vim.eval('&g:' + option)
+
+ def vim_setoption(option, value):
+ vim.command('let &g:{option} = {value}'.format(
+ option=option, value=python_to_vim(value)))
+
+
+if hasattr(vim, 'tabpages'):
+ current_tabpage = lambda: vim.current.tabpage
+ list_tabpages = lambda: vim.tabpages
+
+ def list_tabpage_buffers_segment_info(segment_info):
+ return (
+ {'buffer': window.buffer, 'bufnr': window.buffer.number}
+ for window in segment_info['tabpage'].windows
+ )
+else:
+ class FalseObject(object):
+ @staticmethod
+ def __nonzero__():
+ return False
+
+ __bool__ = __nonzero__
+
+ def get_buffer(number):
+ for buffer in vim.buffers:
+ if buffer.number == number:
+ return buffer
+ raise KeyError(number)
+
+ class WindowVars(object):
+ __slots__ = ('tabnr', 'winnr')
+
+ def __init__(self, window):
+ self.tabnr = window.tabnr
+ self.winnr = window.number
+
+ def __getitem__(self, key):
+ has_key = vim.eval('has_key(gettabwinvar({0}, {1}, ""), "{2}")'.format(self.tabnr, self.winnr, key))
+ if has_key == '0':
+ raise KeyError
+ return vim.eval('gettabwinvar({0}, {1}, "{2}")'.format(self.tabnr, self.winnr, key))
+
+ def get(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ class Window(FalseObject):
+ __slots__ = ('tabnr', 'number', '_vars')
+
+ def __init__(self, tabnr, number):
+ self.tabnr = tabnr
+ self.number = number
+ self.vars = WindowVars(self)
+
+ @property
+ def buffer(self):
+ return get_buffer(int(vim.eval('tabpagebuflist({0})[{1}]'.format(self.tabnr, self.number - 1))))
+
+ class Tabpage(FalseObject):
+ __slots__ = ('number',)
+
+ def __init__(self, number):
+ self.number = number
+
+ def __eq__(self, tabpage):
+ if not isinstance(tabpage, Tabpage):
+ raise NotImplementedError
+ return self.number == tabpage.number
+
+ @property
+ def window(self):
+ return Window(self.number, int(vim.eval('tabpagewinnr({0})'.format(self.number))))
+
+ def _last_tab_nr():
+ return int(vim.eval('tabpagenr("$")'))
+
+ def current_tabpage():
+ return Tabpage(int(vim.eval('tabpagenr()')))
+
+ def list_tabpages():
+ return [Tabpage(nr) for nr in range(1, _last_tab_nr() + 1)]
+
+ class TabBufSegmentInfo(dict):
+ def __getitem__(self, key):
+ try:
+ return super(TabBufSegmentInfo, self).__getitem__(key)
+ except KeyError:
+ if key != 'buffer':
+ raise
+ else:
+ buffer = get_buffer(super(TabBufSegmentInfo, self).__getitem__('bufnr'))
+ self['buffer'] = buffer
+ return buffer
+
+ def list_tabpage_buffers_segment_info(segment_info):
+ return (
+ TabBufSegmentInfo(bufnr=int(bufnrstr))
+ for bufnrstr in vim.eval('tabpagebuflist({0})'.format(segment_info['tabnr']))
+ )
+
+
+class VimEnviron(object):
+ @staticmethod
+ def __getitem__(key):
+ return vim.eval('$' + key)
+
+ @staticmethod
+ def get(key, default=None):
+ return vim.eval('$' + key) or default
+
+ @staticmethod
+ def __setitem__(key, value):
+ return vim.command(
+ 'let ${0}="{1}"'.format(
+ key,
+ value.replace('"', '\\"')
+ .replace('\\', '\\\\')
+ .replace('\n', '\\n')
+ .replace('\0', '')
+ )
+ )
+
+
+if sys.version_info < (3,):
+ def buffer_name(segment_info):
+ return segment_info['buffer'].name
+else:
+ vim_bufname = vim_get_func('bufname', rettype='bytes')
+
+ def buffer_name(segment_info):
+ try:
+ name = segment_info['buffer'].name
+ except UnicodeDecodeError:
+ return vim_bufname(segment_info['bufnr'])
+ else:
+ return name.encode(segment_info['encoding']) if name else None
+
+
+vim_strtrans = vim_get_func('strtrans', rettype='unicode')
+
+
+def powerline_vim_strtrans_error(e):
+ if not isinstance(e, UnicodeDecodeError):
+ raise NotImplementedError
+ text = vim_strtrans(e.object[e.start:e.end])
+ return (text, e.end)
+
+
+codecs.register_error('powerline_vim_strtrans_error', powerline_vim_strtrans_error)
+
+
+did_autocmd = False
+buffer_caches = []
+
+
+def register_buffer_cache(cachedict):
+ global did_autocmd
+ global buffer_caches
+ from powerline.vim import get_default_pycmd, pycmd
+ if not did_autocmd:
+ import __main__
+ __main__.powerline_on_bwipe = on_bwipe
+ vim.command('augroup Powerline')
+ vim.command(' autocmd! BufWipeout * :{pycmd} powerline_on_bwipe()'.format(
+ pycmd=(pycmd or get_default_pycmd())))
+ vim.command('augroup END')
+ did_autocmd = True
+ buffer_caches.append(cachedict)
+ return cachedict
+
+
+def on_bwipe():
+ global buffer_caches
+ bufnr = int(vim.eval('expand("<abuf>")'))
+ for cachedict in buffer_caches:
+ cachedict.pop(bufnr, None)
+
+
+environ = VimEnviron()
+
+
+def create_ruby_dpowerline():
+ vim.command((
+ '''
+ ruby
+ if $powerline == nil
+ class Powerline
+ end
+ $powerline = Powerline.new
+ end
+ '''
+ ))
diff --git a/powerline/bindings/vim/autoload/powerline/debug.vim b/powerline/bindings/vim/autoload/powerline/debug.vim
new file mode 100644
index 0000000..244319a
--- /dev/null
+++ b/powerline/bindings/vim/autoload/powerline/debug.vim
@@ -0,0 +1,20 @@
+python import cProfile
+python powerline_pr = cProfile.Profile()
+
+function powerline#debug#profile_pyeval(s)
+ python powerline_pr.enable()
+ try
+ let ret = pyeval(a:s)
+ finally
+ python powerline_pr.disable()
+ endtry
+ return ret
+endfunction
+
+function powerline#debug#write_profile(fname)
+ python import vim
+ python powerline_pr.dump_stats(vim.eval('a:fname'))
+ python powerline_pr = cProfile.Profile()
+endfunction
+
+command -nargs=1 -complete=file WriteProfiling :call powerline#debug#write_profile(<q-args>)
diff --git a/powerline/bindings/vim/plugin/powerline.vim b/powerline/bindings/vim/plugin/powerline.vim
new file mode 100644
index 0000000..b06a389
--- /dev/null
+++ b/powerline/bindings/vim/plugin/powerline.vim
@@ -0,0 +1,169 @@
+if exists('g:powerline_loaded')
+ finish
+endif
+let g:powerline_loaded = 1
+
+if exists('g:powerline_pycmd')
+ let s:pycmd = substitute(g:powerline_pycmd, '\v\C^(py)%[thon](3?)$', '\1\2', '')
+ if s:pycmd is# 'py'
+ let s:has_python = has('python')
+ let s:pyeval = get(g:, 'powerline_pyeval', 'pyeval')
+ elseif s:pycmd is# 'py3'
+ let s:has_python = has('python3')
+ let s:pyeval = 'py3eval'
+ let s:pyeval = get(g:, 'powerline_pyeval', 'py3eval')
+ else
+ if !exists('g:powerline_pyeval')
+ echohl ErrorMsg
+ echomsg 'g:powerline_pycmd was set to an unknown values, but g:powerline_pyeval'
+ echomsg 'was not set. You should either set g:powerline_pycmd to "py3" or "py",'
+ echomsg 'specify g:powerline_pyeval explicitly or unset both and let powerline'
+ echomsg 'figure them out.'
+ echohl None
+ unlet s:pycmd
+ finish
+ endif
+ let s:pyeval = g:powerline_pyeval
+ let s:has_python = 1
+ endif
+elseif has('python3')
+ let s:has_python = 1
+ let s:pycmd = 'py3'
+ let s:pyeval = get(g:, 'powerline_pyeval', 'py3eval')
+elseif has('python')
+ let s:has_python = 1
+ let s:pycmd = 'py'
+ let s:pyeval = get(g:, 'powerline_pyeval', 'pyeval')
+else
+ let s:has_python = 0
+endif
+
+if !s:has_python
+ if !exists('g:powerline_no_python_error')
+ echohl ErrorMsg
+ echomsg 'You need vim compiled with Python 2.6, 2.7 or 3.2 and later support'
+ echomsg 'for Powerline to work. Please consult the documentation for more'
+ echomsg 'details.'
+ echohl None
+ endif
+ unlet s:has_python
+ finish
+endif
+unlet s:has_python
+
+let s:import_cmd = 'from powerline.vim import VimPowerline'
+function s:rcmd(s)
+ if !exists('s:pystr')
+ let s:pystr = a:s . "\n"
+ else
+ let s:pystr = s:pystr . a:s . "\n"
+ endif
+endfunction
+try
+ let s:can_replace_pyeval = !exists('g:powerline_pyeval')
+ call s:rcmd('try:')
+ call s:rcmd(' powerline_appended_path = None')
+ call s:rcmd(' try:')
+ call s:rcmd(' '.s:import_cmd.'')
+ call s:rcmd(' except ImportError:')
+ call s:rcmd(' import sys, vim')
+ call s:rcmd(' powerline_appended_path = vim.eval("expand(\"<sfile>:h:h:h:h:h\")")')
+ call s:rcmd(' sys.path.append(powerline_appended_path)')
+ call s:rcmd(' '.s:import_cmd.'')
+ call s:rcmd(' import vim')
+ call s:rcmd(' powerline_instance = VimPowerline()')
+ call s:rcmd(' powerline_instance.setup(pyeval=vim.eval("s:pyeval"), pycmd=vim.eval("s:pycmd"), can_replace_pyeval=int(vim.eval("s:can_replace_pyeval")))')
+ call s:rcmd(' del VimPowerline')
+ call s:rcmd(' del powerline_instance')
+ call s:rcmd('except Exception:')
+ call s:rcmd(' import traceback, sys')
+ call s:rcmd(' traceback.print_exc(file=sys.stdout)')
+ call s:rcmd(' raise')
+ execute s:pycmd s:pystr
+ unlet s:pystr
+ let s:launched = 1
+finally
+ unlet s:can_replace_pyeval
+ unlet s:import_cmd
+ if !exists('s:launched')
+ unlet s:pystr
+ echohl ErrorMsg
+ echomsg 'An error occurred while importing powerline module.'
+ echomsg 'This could be caused by invalid sys.path setting,'
+ echomsg 'or by an incompatible Python version (powerline requires'
+ echomsg 'Python 2.6, 2.7 or 3.2 and later to work). Please consult'
+ echomsg 'the troubleshooting section in the documentation for'
+ echomsg 'possible solutions.'
+ if s:pycmd is# 'py' && has('python3')
+ echomsg 'If powerline on your system is installed for python 3 only you'
+ echomsg 'should set g:powerline_pycmd to "py3" to make it load correctly.'
+ endif
+ echohl None
+ call s:rcmd('def powerline_troubleshoot():')
+ call s:rcmd(' import sys')
+ call s:rcmd(' import vim')
+ call s:rcmd(' if sys.version_info < (2, 6):')
+ call s:rcmd(' print("Too old python version: " + sys.version + " (first supported is 2.6)")')
+ call s:rcmd(' elif sys.version_info[0] == 3 and sys.version_info[1] < 2:')
+ call s:rcmd(' print("Too old python 3 version: " + sys.version + " (first supported is 3.2)")')
+ call s:rcmd(' try:')
+ call s:rcmd(' import powerline')
+ call s:rcmd(' except ImportError:')
+ call s:rcmd(' print("Unable to import powerline, is it installed?")')
+ call s:rcmd(' else:')
+ call s:rcmd(' if not vim.eval(''expand("<sfile>")'').startswith("/usr/"):')
+ call s:rcmd(' import os')
+ call s:rcmd(' powerline_dir = os.path.realpath(os.path.normpath(powerline.__file__))')
+ call s:rcmd(' powerline_dir = os.path.dirname(powerline.__file__)')
+ call s:rcmd(' this_dir = os.path.realpath(os.path.normpath(vim.eval(''expand("<sfile>:p")'')))')
+ call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings/vim/plugin
+ call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings/vim
+ call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings
+ call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline
+ call s:rcmd(' if os.path.basename(this_dir) != "powerline":')
+ call s:rcmd(' print("Check your installation:")')
+ call s:rcmd(' print("this script is not in powerline[/bindings/vim/plugin] directory,")')
+ call s:rcmd(' print("neither it is installed system-wide")')
+ call s:rcmd(' real_powerline_dir = os.path.realpath(powerline_dir)')
+ call s:rcmd(' real_this_dir = os.path.realpath(this_dir)')
+ call s:rcmd(' this_dir_par = os.path.dirname(real_this_dir)')
+ call s:rcmd(' powerline_appended_path = globals().get("powerline_appended_path")')
+ call s:rcmd(' if powerline_appended_path is not None and this_dir_par != powerline_appended_path:')
+ call s:rcmd(' print("Check your installation: this script is symlinked somewhere")')
+ call s:rcmd(' print("where powerline is not present: {0!r} != {1!r}.".format(')
+ call s:rcmd(' real_this_dir, powerline_appended_path))')
+ call s:rcmd(' elif real_powerline_dir != real_this_dir:')
+ call s:rcmd(' print("It appears that you have two powerline versions installed:")')
+ call s:rcmd(' print("one in " + real_powerline_dir + ", other in " + real_this_dir + ".")')
+ call s:rcmd(' print("You should remove one of this. Check out troubleshooting section,")')
+ call s:rcmd(' print("it contains some information about the alternatives.")')
+ call s:rcmd(' try:')
+ call s:rcmd(' from powerline.lint import check')
+ call s:rcmd(' except ImportError:')
+ call s:rcmd(' print("Failed to import powerline.lint.check, cannot run powerline-lint")')
+ call s:rcmd(' else:')
+ call s:rcmd(' try:')
+ call s:rcmd(' paths = powerline_instance.get_config_paths()')
+ call s:rcmd(' except NameError:')
+ call s:rcmd(' pass')
+ call s:rcmd(' else:')
+ call s:rcmd(' from powerline.lint.markedjson.error import echoerr')
+ call s:rcmd(' ee = lambda *args, **kwargs: echoerr(*args, stream=sys.stdout, **kwargs)')
+ call s:rcmd(' check(paths=paths, echoerr=ee, require_ext="vim")')
+ call s:rcmd('try:')
+ call s:rcmd(' powerline_troubleshoot()')
+ call s:rcmd('finally:')
+ call s:rcmd(' del powerline_troubleshoot')
+ execute s:pycmd s:pystr
+ unlet s:pystr
+ unlet s:pycmd
+ unlet s:pyeval
+ delfunction s:rcmd
+ finish
+ else
+ unlet s:launched
+ endif
+ unlet s:pycmd
+ unlet s:pyeval
+ delfunction s:rcmd
+endtry
diff --git a/powerline/bindings/wm/__init__.py b/powerline/bindings/wm/__init__.py
new file mode 100644
index 0000000..d2c6f30
--- /dev/null
+++ b/powerline/bindings/wm/__init__.py
@@ -0,0 +1,72 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import re
+
+from powerline.theme import requires_segment_info
+from powerline.lib.shell import run_cmd
+from powerline.bindings.wm.awesome import AwesomeThread
+
+
+DEFAULT_UPDATE_INTERVAL = 0.5
+
+
+conn = None
+
+
+def i3_subscribe(conn, event, callback):
+ '''Subscribe to i3 workspace event
+
+ :param conn:
+ Connection returned by :py:func:`get_i3_connection`.
+ :param str event:
+ Event to subscribe to, e.g. ``'workspace'``.
+ :param func callback:
+ Function to run on event.
+ '''
+ conn.on(event, callback)
+
+ from threading import Thread
+
+ class I3Thread(Thread):
+ daemon = True
+
+ def __init__(self, conn):
+ super(I3Thread, self).__init__()
+ self.__conn = conn
+
+ def run(self):
+ self.__conn.main()
+
+ thread = I3Thread(conn=conn)
+
+ thread.start()
+
+
+def get_i3_connection():
+ '''Return a valid, cached i3 Connection instance
+ '''
+ global conn
+ if not conn:
+ import i3ipc
+ conn = i3ipc.Connection()
+ return conn
+
+
+XRANDR_OUTPUT_RE = re.compile(r'^(?P<name>[0-9A-Za-z-]+) connected(?P<primary> primary)? (?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+)', re.MULTILINE)
+
+
+def get_connected_xrandr_outputs(pl):
+ '''Iterate over xrandr outputs
+
+ Outputs are represented by a dictionary with ``name``, ``width``,
+ ``height``, ``primary``, ``x`` and ``y`` keys.
+ '''
+ return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer(
+ run_cmd(pl, ['xrandr', '-q'])
+ ))
+
+
+wm_threads = {
+ 'awesome': AwesomeThread,
+}
diff --git a/powerline/bindings/wm/awesome.py b/powerline/bindings/wm/awesome.py
new file mode 100644
index 0000000..b6e07f2
--- /dev/null
+++ b/powerline/bindings/wm/awesome.py
@@ -0,0 +1,59 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+
+from threading import Thread, Event
+from time import sleep
+from subprocess import Popen, PIPE
+
+from powerline import Powerline
+from powerline.lib.monotonic import monotonic
+
+
+def read_to_log(pl, client):
+ for line in client.stdout:
+ if line:
+ pl.info(line, prefix='awesome-client')
+ for line in client.stderr:
+ if line:
+ pl.error(line, prefix='awesome-client')
+ if client.wait():
+ pl.error('Client exited with {0}', client.returncode, prefix='awesome')
+
+
+def run(thread_shutdown_event=None, pl_shutdown_event=None, pl_config_loader=None,
+ interval=None):
+ powerline = Powerline(
+ 'wm',
+ renderer_module='pango_markup',
+ shutdown_event=pl_shutdown_event,
+ config_loader=pl_config_loader,
+ )
+ powerline.update_renderer()
+
+ if not thread_shutdown_event:
+ thread_shutdown_event = powerline.shutdown_event
+
+ while not thread_shutdown_event.is_set():
+ # powerline.update_interval may change over time
+ used_interval = interval or powerline.update_interval
+ start_time = monotonic()
+ s = powerline.render(side='right')
+ request = 'powerline_widget:set_markup(\'' + s.translate({'\'': '\\\'', '\\': '\\\\'}) + '\')\n'
+ client = Popen(['awesome-client'], shell=False, stdout=PIPE, stderr=PIPE, stdin=PIPE)
+ client.stdin.write(request.encode('utf-8'))
+ client.stdin.close()
+ read_to_log(powerline.pl, client)
+ thread_shutdown_event.wait(max(used_interval - (monotonic() - start_time), 0.1))
+
+
+class AwesomeThread(Thread):
+ __slots__ = ('powerline_shutdown_event',)
+
+ def __init__(self, **kwargs):
+ super(AwesomeThread, self).__init__()
+ self.powerline_run_kwargs = kwargs
+
+ def run(self):
+ run(**self.powerline_run_kwargs)
diff --git a/powerline/bindings/zsh/__init__.py b/powerline/bindings/zsh/__init__.py
new file mode 100644
index 0000000..44f5c69
--- /dev/null
+++ b/powerline/bindings/zsh/__init__.py
@@ -0,0 +1,225 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import atexit
+
+from weakref import WeakValueDictionary, ref
+
+import zsh
+
+from powerline.shell import ShellPowerline
+from powerline.lib.overrides import parsedotval, parse_override_var
+from powerline.lib.unicode import unicode, u
+from powerline.lib.encoding import (get_preferred_output_encoding,
+ get_preferred_environment_encoding)
+from powerline.lib.dict import mergeargs
+
+
+used_powerlines = WeakValueDictionary()
+
+
+def shutdown():
+ for powerline in tuple(used_powerlines.values()):
+ powerline.shutdown()
+
+
+def get_var_config(var):
+ try:
+ val = zsh.getvalue(var)
+ if isinstance(val, dict):
+ return mergeargs([parsedotval((u(k), u(v))) for k, v in val.items()])
+ elif isinstance(val, (unicode, str, bytes)):
+ return mergeargs(parse_override_var(u(val)))
+ else:
+ return None
+ except:
+ return None
+
+
+class Args(object):
+ __slots__ = ('last_pipe_status', 'last_exit_code')
+ ext = ['shell']
+ renderer_module = '.zsh'
+
+ @property
+ def config_override(self):
+ return get_var_config('POWERLINE_CONFIG_OVERRIDES')
+
+ @property
+ def theme_override(self):
+ return get_var_config('POWERLINE_THEME_OVERRIDES')
+
+ @property
+ def config_path(self):
+ try:
+ ret = zsh.getvalue('POWERLINE_CONFIG_PATHS')
+ except IndexError:
+ return None
+ else:
+ if isinstance(ret, (unicode, str, bytes)):
+ return [
+ path
+ for path in ret.split((b':' if isinstance(ret, bytes) else ':'))
+ if path
+ ]
+ else:
+ return ret
+
+ @property
+ def jobnum(self):
+ return zsh.getvalue('_POWERLINE_JOBNUM')
+
+
+def string(s):
+ if type(s) is bytes:
+ return s.decode(get_preferred_environment_encoding(), 'replace')
+ else:
+ return str(s)
+
+
+class Environment(object):
+ @staticmethod
+ def __getitem__(key):
+ try:
+ return string(zsh.getvalue(key))
+ except IndexError as e:
+ raise KeyError(*e.args)
+
+ @staticmethod
+ def get(key, default=None):
+ try:
+ return string(zsh.getvalue(key))
+ except IndexError:
+ return default
+
+ @staticmethod
+ def __contains__(key):
+ try:
+ zsh.getvalue(key)
+ return True
+ except IndexError:
+ return False
+
+
+environ = Environment()
+
+
+if hasattr(zsh, 'expand') and zsh.expand('${:-}') == '':
+ zsh_expand = zsh.expand
+else:
+ def zsh_expand(s):
+ zsh.eval('local _POWERLINE_REPLY="' + s + '"')
+ ret = zsh.getvalue('_POWERLINE_REPLY')
+ zsh.setvalue('_POWERLINE_REPLY', None)
+ return ret
+
+
+class ZshPowerline(ShellPowerline):
+ def init(self, **kwargs):
+ super(ZshPowerline, self).init(Args(), **kwargs)
+
+ def precmd(self):
+ self.args.last_pipe_status = zsh.pipestatus()
+ self.args.last_exit_code = zsh.last_exit_code()
+
+ def do_setup(self, zsh_globals):
+ set_prompt(self, 'PS1', 'left', None, above=True)
+ set_prompt(self, 'RPS1', 'right', None)
+ set_prompt(self, 'PS2', 'left', 'continuation')
+ set_prompt(self, 'RPS2', 'right', 'continuation')
+ set_prompt(self, 'PS3', 'left', 'select')
+ used_powerlines[id(self)] = self
+ zsh_globals['_powerline'] = self
+
+
+class Prompt(object):
+ __slots__ = ('powerline', 'side', 'savedpsvar', 'savedps', 'args', 'theme', 'above', '__weakref__')
+
+ def __init__(self, powerline, side, theme, savedpsvar=None, savedps=None, above=False):
+ self.powerline = powerline
+ self.side = side
+ self.above = above
+ self.savedpsvar = savedpsvar
+ self.savedps = savedps
+ self.args = powerline.args
+ self.theme = theme
+
+ def __str__(self):
+ parser_state = u(zsh_expand('${(%):-%_}'))
+ shortened_path = u(zsh_expand('${(%):-%~}'))
+ try:
+ mode = u(zsh.getvalue('_POWERLINE_MODE'))
+ except IndexError:
+ mode = None
+ try:
+ default_mode = u(zsh.getvalue('_POWERLINE_DEFAULT_MODE'))
+ except IndexError:
+ default_mode = None
+ segment_info = {
+ 'args': self.args,
+ 'environ': environ,
+ 'client_id': 1,
+ 'local_theme': self.theme,
+ 'parser_state': parser_state,
+ 'shortened_path': shortened_path,
+ 'mode': mode,
+ 'default_mode': default_mode,
+ }
+ try:
+ zle_rprompt_indent = zsh.getvalue('ZLE_RPROMPT_INDENT')
+ except IndexError:
+ zle_rprompt_indent = 1
+ r = ''
+ if self.above:
+ for line in self.powerline.render_above_lines(
+ width=zsh.columns() - zle_rprompt_indent,
+ segment_info=segment_info,
+ ):
+ if line:
+ r += line + '\n'
+ r += self.powerline.render(
+ width=zsh.columns(),
+ side=self.side,
+ segment_info=segment_info,
+ mode=mode,
+ )
+ if type(r) is not str:
+ if type(r) is bytes:
+ return r.decode(get_preferred_output_encoding(), 'replace')
+ else:
+ return r.encode(get_preferred_output_encoding(), 'replace')
+ return r
+
+ def __del__(self):
+ if self.savedps:
+ zsh.setvalue(self.savedpsvar, self.savedps)
+ self.powerline.shutdown()
+
+
+def set_prompt(powerline, psvar, side, theme, above=False):
+ try:
+ savedps = zsh.getvalue(psvar)
+ except IndexError:
+ savedps = None
+ zpyvar = 'ZPYTHON_POWERLINE_' + psvar
+ prompt = Prompt(powerline, side, theme, psvar, savedps, above)
+ zsh.setvalue(zpyvar, None)
+ zsh.set_special_string(zpyvar, prompt)
+ zsh.setvalue(psvar, '${' + zpyvar + '}')
+ return ref(prompt)
+
+
+def reload():
+ for powerline in tuple(used_powerlines.values()):
+ powerline.reload()
+
+
+def reload_config():
+ for powerline in used_powerlines.values():
+ powerline.create_renderer(load_main=True, load_colors=True, load_colorscheme=True, load_theme=True)
+
+
+def setup(zsh_globals):
+ powerline = ZshPowerline()
+ powerline.setup(zsh_globals)
+ atexit.register(shutdown)
diff --git a/powerline/bindings/zsh/powerline.zsh b/powerline/bindings/zsh/powerline.zsh
new file mode 100644
index 0000000..ff46cb0
--- /dev/null
+++ b/powerline/bindings/zsh/powerline.zsh
@@ -0,0 +1,216 @@
+local _POWERLINE_SOURCED="$0:A"
+
+_powerline_columns_fallback() {
+ if which stty &>/dev/null ; then
+ local cols="$(stty size 2>/dev/null)"
+ if ! test -z "$cols" ; then
+ echo "${cols#* }"
+ return 0
+ fi
+ fi
+ echo 0
+ return 0
+}
+
+_powerline_append_precmd_function() {
+ if test -z "${precmd_functions[(re)$1]}" ; then
+ precmd_functions+=( $1 )
+ fi
+}
+
+integer -g _POWERLINE_JOBNUM=0
+
+_powerline_tmux_pane() {
+ local -x TMUX="$_POWERLINE_TMUX"
+ echo "${TMUX_PANE:-`tmux display -p "#D"`}" | tr -d ' %'
+}
+
+_powerline_tmux_pane() {
+ local -x TMUX="$_POWERLINE_TMUX"
+ echo "${TMUX_PANE:-`tmux display -p "#D"`}" | tr -d ' %'
+}
+
+_powerline_init_tmux_support() {
+ emulate -L zsh
+ if test -n "$TMUX" && tmux refresh -S &>/dev/null ; then
+ # TMUX variable may be unset to create new tmux session inside this one
+ typeset -g _POWERLINE_TMUX="$TMUX"
+
+ function -g _powerline_tmux_setenv() {
+ emulate -L zsh
+ local -x TMUX="$_POWERLINE_TMUX"
+ tmux setenv -g TMUX_"$1"_$(_powerline_tmux_pane) "$2"
+ tmux refresh -S
+ }
+
+ function -g _powerline_tmux_set_pwd() {
+ _powerline_tmux_setenv PWD "$PWD"
+ }
+
+ function -g _powerline_tmux_set_columns() {
+ _powerline_tmux_setenv COLUMNS "${COLUMNS:-$(_powerline_columns_fallback)}"
+ }
+
+ chpwd_functions+=( _powerline_tmux_set_pwd )
+ trap '_powerline_tmux_set_columns' SIGWINCH
+ _powerline_tmux_set_columns
+ _powerline_tmux_set_pwd
+ fi
+}
+
+_powerline_init_modes_support() {
+ emulate -L zsh
+
+ test -z "$ZSH_VERSION" && return 0
+
+ local -a vs
+ vs=( ${(s:.:)ZSH_VERSION} )
+
+ # Mode support requires >=zsh-4.3.11
+ if (( vs[1] < 4 || (vs[1] == 4 && (vs[2] < 3 || (vs[2] == 3 && vs[3] < 11))) )) ; then
+ return 0
+ fi
+
+ function -g _powerline_get_main_keymap_name() {
+ REPLY="${${(Q)${${(z)${"$(bindkey -lL main)"}}[3]}}:-.safe}"
+ }
+
+ function -g _powerline_set_true_keymap_name() {
+ typeset -g _POWERLINE_MODE="${1}"
+ local plm_bk="$(bindkey -lL ${_POWERLINE_MODE})"
+ if [[ $plm_bk = 'bindkey -A'* ]] ; then
+ _powerline_set_true_keymap_name ${(Q)${${(z)plm_bk}[3]}}
+ fi
+ }
+
+ function -g _powerline_zle_keymap_select() {
+ _powerline_set_true_keymap_name $KEYMAP
+ zle reset-prompt
+ test -z "$_POWERLINE_SAVE_WIDGET" || zle $_POWERLINE_SAVE_WIDGET
+ }
+
+ function -g _powerline_set_main_keymap_name() {
+ local REPLY
+ _powerline_get_main_keymap_name
+ _powerline_set_true_keymap_name "$REPLY"
+ }
+
+ _powerline_add_widget zle-keymap-select _powerline_zle_keymap_select
+ _powerline_set_main_keymap_name
+
+ if [[ "$_POWERLINE_MODE" != vi* ]] ; then
+ typeset -g _POWERLINE_DEFAULT_MODE="$_POWERLINE_MODE"
+ fi
+
+ _powerline_append_precmd_function _powerline_set_main_keymap_name
+}
+
+_powerline_set_jobnum() {
+ # If you are wondering why I am not using the same code as I use for bash
+ # ($(jobs|wc -l)): consider the following test:
+ # echo abc | less
+ # <C-z>
+ # . This way jobs will print
+ # [1] + done echo abc |
+ # suspended less -M
+ # ([ is in first column). You see: any line counting thingie will return
+ # wrong number of jobs. You need to filter the lines first. Or not use
+ # jobs built-in at all.
+ integer -g _POWERLINE_JOBNUM=${(%):-%j}
+}
+
+_powerline_update_counter() {
+ zpython '_powerline.precmd()'
+}
+
+_powerline_setup_prompt() {
+ emulate -L zsh
+
+ _powerline_append_precmd_function _powerline_set_jobnum
+
+ typeset -g VIRTUAL_ENV_DISABLE_PROMPT=1
+
+ if test -z "${POWERLINE_NO_ZSH_ZPYTHON}" && { zmodload libzpython || zmodload zsh/zpython } &>/dev/null ; then
+ _powerline_append_precmd_function _powerline_update_counter
+ zpython 'from powerline.bindings.zsh import setup as _powerline_setup'
+ zpython '_powerline_setup(globals())'
+ zpython 'del _powerline_setup'
+ powerline-reload() {
+ zpython 'from powerline.bindings.zsh import reload as _powerline_reload'
+ zpython '_powerline_reload()'
+ zpython 'del _powerline_reload'
+ }
+ powerline-reload-config() {
+ zpython 'from powerline.bindings.zsh import reload_config as _powerline_reload_config'
+ zpython '_powerline_reload_config()'
+ zpython 'del _powerline_reload_config'
+ }
+ else
+ if test -z "${POWERLINE_COMMAND}" ; then
+ typeset -g POWERLINE_COMMAND="$($POWERLINE_CONFIG_COMMAND shell command)"
+ fi
+
+ local add_args='-r .zsh'
+ add_args+=' --last-exit-code=$?'
+ add_args+=' --last-pipe-status="$pipestatus"'
+ add_args+=' --renderer-arg="client_id=$$"'
+ add_args+=' --renderer-arg="shortened_path=${(%):-%~}"'
+ add_args+=' --jobnum=$_POWERLINE_JOBNUM'
+ add_args+=' --renderer-arg="mode=$_POWERLINE_MODE"'
+ add_args+=' --renderer-arg="default_mode=$_POWERLINE_DEFAULT_MODE"'
+ local new_args_2=' --renderer-arg="parser_state=${(%%):-%_}"'
+ new_args_2+=' --renderer-arg="local_theme=continuation"'
+ local add_args_3=$add_args' --renderer-arg="local_theme=select"'
+ local add_args_2=$add_args$new_args_2
+ add_args+=' --width=$(( ${COLUMNS:-$(_powerline_columns_fallback)} - ${ZLE_RPROMPT_INDENT:-1} ))'
+ local add_args_r2=$add_args$new_args_2
+ typeset -g PS1='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell aboveleft '$add_args')'
+ typeset -g RPS1='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell right '$add_args')'
+ typeset -g PS2='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell left '$add_args_2')'
+ typeset -g RPS2='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell right '$add_args_r2')'
+ typeset -g PS3='$("$POWERLINE_COMMAND" $=POWERLINE_COMMAND_ARGS shell left '$add_args_3')'
+ fi
+}
+
+_powerline_add_widget() {
+ local widget="$1"
+ local function="$2"
+ local old_widget_command="$(zle -l -L $widget)"
+ if [[ "$old_widget_command" = "zle -N $widget $function" ]] ; then
+ return 0
+ elif [[ -z "$old_widget_command" ]] ; then
+ zle -N $widget $function
+ else
+ local save_widget="_powerline_save_$widget"
+ local -i i=0
+ while ! test -z "$(zle -l -L $save_widget)" ; do
+ save_widget="${save_widget}_$i"
+ (( i++ ))
+ done
+ # If widget was defined with `zle -N widget` (without `function`
+ # argument) then this function will be handy.
+ eval "function $save_widget() { emulate -L zsh; $widget \$@ }"
+ eval "${old_widget_command/$widget/$save_widget}"
+ zle -N $widget $function
+ typeset -g _POWERLINE_SAVE_WIDGET="$save_widget"
+ fi
+}
+
+if test -z "${POWERLINE_CONFIG_COMMAND}" ; then
+ if which powerline-config >/dev/null 2>/dev/null ; then
+ typeset -g POWERLINE_CONFIG_COMMAND=powerline-config
+ else
+ typeset -g POWERLINE_CONFIG_COMMAND="${_POWERLINE_SOURCED:h:h:h:h}/scripts/powerline-config"
+ fi
+fi
+
+setopt promptpercent
+setopt promptsubst
+
+if "${POWERLINE_CONFIG_COMMAND}" shell --shell=zsh uses prompt ; then
+ _powerline_setup_prompt
+ _powerline_init_modes_support
+fi
+if "${POWERLINE_CONFIG_COMMAND}" shell --shell=zsh uses tmux ; then
+ _powerline_init_tmux_support
+fi
diff --git a/powerline/colorscheme.py b/powerline/colorscheme.py
new file mode 100644
index 0000000..66416b5
--- /dev/null
+++ b/powerline/colorscheme.py
@@ -0,0 +1,147 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from copy import copy
+
+from powerline.lib.unicode import unicode
+
+
+DEFAULT_MODE_KEY = None
+ATTR_BOLD = 1
+ATTR_ITALIC = 2
+ATTR_UNDERLINE = 4
+
+
+def get_attrs_flag(attrs):
+ '''Convert an attribute array to a renderer flag.'''
+ attrs_flag = 0
+ if 'bold' in attrs:
+ attrs_flag |= ATTR_BOLD
+ if 'italic' in attrs:
+ attrs_flag |= ATTR_ITALIC
+ if 'underline' in attrs:
+ attrs_flag |= ATTR_UNDERLINE
+ return attrs_flag
+
+
+def pick_gradient_value(grad_list, gradient_level):
+ '''Given a list of colors and gradient percent, return a color that should be used.
+
+ Note: gradient level is not checked for being inside [0, 100] interval.
+ '''
+ return grad_list[int(round(gradient_level * (len(grad_list) - 1) / 100))]
+
+
+class Colorscheme(object):
+ def __init__(self, colorscheme_config, colors_config):
+ '''Initialize a colorscheme.'''
+ self.colors = {}
+ self.gradients = {}
+
+ self.groups = colorscheme_config['groups']
+ self.translations = colorscheme_config.get('mode_translations', {})
+
+ # Create a dict of color tuples with both a cterm and hex value
+ for color_name, color in colors_config['colors'].items():
+ try:
+ self.colors[color_name] = (color[0], int(color[1], 16))
+ except TypeError:
+ self.colors[color_name] = (color, cterm_to_hex[color])
+
+ # Create a dict of gradient names with two lists: for cterm and hex
+ # values. Two lists in place of one list of pairs were chosen because
+ # true colors allow more precise gradients.
+ for gradient_name, gradient in colors_config['gradients'].items():
+ if len(gradient) == 2:
+ self.gradients[gradient_name] = (
+ (gradient[0], [int(color, 16) for color in gradient[1]]))
+ else:
+ self.gradients[gradient_name] = (
+ (gradient[0], [cterm_to_hex[color] for color in gradient[0]]))
+
+ def get_gradient(self, gradient, gradient_level):
+ if gradient in self.gradients:
+ return tuple((pick_gradient_value(grad_list, gradient_level) for grad_list in self.gradients[gradient]))
+ else:
+ return self.colors[gradient]
+
+ def get_group_props(self, mode, trans, group, translate_colors=True):
+ if isinstance(group, (str, unicode)):
+ try:
+ group_props = trans['groups'][group]
+ except KeyError:
+ try:
+ group_props = self.groups[group]
+ except KeyError:
+ return None
+ else:
+ return self.get_group_props(mode, trans, group_props, True)
+ else:
+ return self.get_group_props(mode, trans, group_props, False)
+ else:
+ if translate_colors:
+ group_props = copy(group)
+ try:
+ ctrans = trans['colors']
+ except KeyError:
+ pass
+ else:
+ for key in ('fg', 'bg'):
+ try:
+ group_props[key] = ctrans[group_props[key]]
+ except KeyError:
+ pass
+ return group_props
+ else:
+ return group
+
+ def get_highlighting(self, groups, mode, gradient_level=None):
+ trans = self.translations.get(mode, {})
+ for group in groups:
+ group_props = self.get_group_props(mode, trans, group)
+ if group_props:
+ break
+ else:
+ raise KeyError('Highlighting groups not found in colorscheme: ' + ', '.join(groups))
+
+ if gradient_level is None:
+ pick_color = self.colors.__getitem__
+ else:
+ pick_color = lambda gradient: self.get_gradient(gradient, gradient_level)
+
+ return {
+ 'fg': pick_color(group_props['fg']),
+ 'bg': pick_color(group_props['bg']),
+ 'attrs': get_attrs_flag(group_props.get('attrs', [])),
+ }
+
+
+# 0 1 2 3 4 5 6 7 8 9
+cterm_to_hex = (
+ 0x000000, 0xc00000, 0x008000, 0x804000, 0x0000c0, 0xc000c0, 0x008080, 0xc0c0c0, 0x808080, 0xff6060, # 0
+ 0x00ff00, 0xffff00, 0x8080ff, 0xff40ff, 0x00ffff, 0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, # 1
+ 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, # 2
+ 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, # 3
+ 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, # 4
+ 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, # 5
+ 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, # 6
+ 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, # 7
+ 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f, # 8
+ 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, # 9
+ 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, # 10
+ 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, # 11
+ 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, # 12
+ 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, # 13
+ 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, # 14
+ 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, # 15
+ 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, # 16
+ 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, # 17
+ 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, # 18
+ 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, # 19
+ 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, # 20
+ 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, # 21
+ 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, # 22
+ 0xffffd7, 0xffffff, 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, # 23
+ 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, # 24
+ 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee # 25
+)
diff --git a/powerline/commands/__init__.py b/powerline/commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/commands/__init__.py
diff --git a/powerline/commands/config.py b/powerline/commands/config.py
new file mode 100644
index 0000000..e9ca5f9
--- /dev/null
+++ b/powerline/commands/config.py
@@ -0,0 +1,109 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (division, absolute_import, print_function)
+
+import argparse
+
+import powerline.bindings.config as config
+
+
+class StrFunction(object):
+ def __init__(self, function, name=None):
+ self.name = name or function.__name__
+ self.function = function
+
+ def __call__(self, *args, **kwargs):
+ self.function(*args, **kwargs)
+
+ def __str__(self):
+ return self.name
+
+
+TMUX_ACTIONS = {
+ 'source': StrFunction(config.source_tmux_files, 'source'),
+ 'setenv': StrFunction(config.init_tmux_environment, 'setenv'),
+ 'setup': StrFunction(config.tmux_setup, 'setup'),
+}
+
+
+SHELL_ACTIONS = {
+ 'command': StrFunction(config.shell_command, 'command'),
+ 'uses': StrFunction(config.uses),
+}
+
+
+class ConfigArgParser(argparse.ArgumentParser):
+ def parse_args(self, *args, **kwargs):
+ ret = super(ConfigArgParser, self).parse_args(*args, **kwargs)
+ if not hasattr(ret, 'function'):
+ # In Python-3* `powerline-config` (without arguments) raises
+ # AttributeError. I have not found any standard way to display same
+ # error message as in Python-2*.
+ self.error('too few arguments')
+ return ret
+
+
+def get_argparser(ArgumentParser=ConfigArgParser):
+ parser = ArgumentParser(description='Script used to obtain powerline configuration.')
+ parser.add_argument(
+ '-p', '--config-path', action='append', metavar='PATH',
+ help='Path to configuration directory. If it is present '
+ 'then configuration files will only be sought in the provided path. '
+ 'May be provided multiple times to search in a list of directories.'
+ )
+ subparsers = parser.add_subparsers()
+ tmux_parser = subparsers.add_parser('tmux', help='Tmux-specific commands')
+ tmux_parser.add_argument(
+ 'function',
+ choices=tuple(TMUX_ACTIONS.values()),
+ metavar='ACTION',
+ type=(lambda v: TMUX_ACTIONS.get(v)),
+ help='If action is `source\' then version-specific tmux configuration '
+ 'files are sourced, if it is `setenv\' then special '
+ '(prefixed with `_POWERLINE\') tmux global environment variables '
+ 'are filled with data from powerline configuration. '
+ 'Action `setup\' is just doing `setenv\' then `source\'.'
+ )
+ tpg = tmux_parser.add_mutually_exclusive_group()
+ tpg.add_argument(
+ '-s', '--source', action='store_true', default=None,
+ help='When using `setup\': always use configuration file sourcing. '
+ 'By default this is determined automatically based on tmux '
+ 'version: this is the default for tmux 1.8 and below.',
+ )
+ tpg.add_argument(
+ '-n', '--no-source', action='store_false', dest='source', default=None,
+ help='When using `setup\': in place of sourcing directly execute '
+ 'configuration files. That is, read each needed '
+ 'powerline-specific configuration file, substitute '
+ '`$_POWERLINE_…\' variables with appropriate values and run '
+ '`tmux config line\'. This is the default behaviour for '
+ 'tmux 1.9 and above.'
+ )
+
+ shell_parser = subparsers.add_parser('shell', help='Shell-specific commands')
+ shell_parser.add_argument(
+ 'function',
+ choices=tuple(SHELL_ACTIONS.values()),
+ type=(lambda v: SHELL_ACTIONS.get(v)),
+ metavar='ACTION',
+ help='If action is `command\' then preferred powerline command is '
+ 'output, if it is `uses\' then powerline-config script will exit '
+ 'with 1 if specified component is disabled and 0 otherwise.',
+ )
+ shell_parser.add_argument(
+ 'component',
+ nargs='?',
+ choices=('tmux', 'prompt'),
+ metavar='COMPONENT',
+ help='Only applicable for `uses\' subcommand: makes `powerline-config\' '
+ 'exit with 0 if specific component is enabled and with 1 otherwise. '
+ '`tmux\' component stands for tmux bindings '
+ '(e.g. those that notify tmux about current directory changes), '
+ '`prompt\' component stands for shell prompt.'
+ )
+ shell_parser.add_argument(
+ '-s', '--shell',
+ metavar='SHELL',
+ help='Shell for which query is run',
+ )
+ return parser
diff --git a/powerline/commands/daemon.py b/powerline/commands/daemon.py
new file mode 100644
index 0000000..7e8c8ab
--- /dev/null
+++ b/powerline/commands/daemon.py
@@ -0,0 +1,24 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (division, absolute_import, print_function)
+
+import argparse
+
+
+def get_argparser(ArgumentParser=argparse.ArgumentParser):
+ parser = ArgumentParser(description='Daemon that improves powerline performance.')
+ parser.add_argument(
+ '--quiet', '-q', action='store_true',
+ help='Without other options: do not complain about already running '
+ 'powerline-daemon instance. '
+ 'Will still exit with 1. '
+ 'With `--kill\' and `--replace\': do not show any messages. '
+ 'With `--foreground\': ignored. '
+ 'Does not silence exceptions in any case.'
+ )
+ parser.add_argument('--socket', '-s', help='Specify socket which will be used for connecting to daemon.')
+ exclusive_group = parser.add_mutually_exclusive_group()
+ exclusive_group.add_argument('--kill', '-k', action='store_true', help='Kill an already running instance.')
+ replace_group = exclusive_group.add_argument_group()
+ replace_group.add_argument('--foreground', '-f', action='store_true', help='Run in the foreground (don’t daemonize).')
+ replace_group.add_argument('--replace', '-r', action='store_true', help='Replace an already running instance.')
+ return parser
diff --git a/powerline/commands/lemonbar.py b/powerline/commands/lemonbar.py
new file mode 100644
index 0000000..547c52c
--- /dev/null
+++ b/powerline/commands/lemonbar.py
@@ -0,0 +1,35 @@
+# vim:fileencoding=utf-8:noet
+# WARNING: using unicode_literals causes errors in argparse
+from __future__ import (division, absolute_import, print_function)
+
+import argparse
+
+
+def get_argparser(ArgumentParser=argparse.ArgumentParser):
+ parser = ArgumentParser(
+ description='Powerline BAR bindings.'
+ )
+ parser.add_argument(
+ '--i3', action='store_true',
+ help='Subscribe for i3 events.'
+ )
+ parser.add_argument(
+ '--height', default='',
+ metavar='PIXELS', help='Bar height.'
+ )
+ parser.add_argument(
+ '--interval', '-i',
+ type=float, default=0.5,
+ metavar='SECONDS', help='Refresh interval.'
+ )
+ parser.add_argument(
+ '--bar-command', '-C',
+ default='lemonbar',
+ metavar='CMD', help='Name of the lemonbar executable to use.'
+ )
+ parser.add_argument(
+ 'args', nargs=argparse.REMAINDER,
+ help='Extra arguments for lemonbar. Should be preceded with ``--`` '
+ 'argument in order not to be confused with script own arguments.'
+ )
+ return parser
diff --git a/powerline/commands/lint.py b/powerline/commands/lint.py
new file mode 100755
index 0000000..8961a65
--- /dev/null
+++ b/powerline/commands/lint.py
@@ -0,0 +1,21 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (division, absolute_import, print_function)
+
+import argparse
+
+
+def get_argparser(ArgumentParser=argparse.ArgumentParser):
+ parser = ArgumentParser(description='Powerline configuration checker.')
+ parser.add_argument(
+ '-p', '--config-path', action='append', metavar='PATH',
+ help='Paths where configuration should be checked, in order. You must '
+ 'supply all paths necessary for powerline to work, '
+ 'checking partial (e.g. only user overrides) configuration '
+ 'is not supported.'
+ )
+ parser.add_argument(
+ '-d', '--debug', action='store_const', const=True,
+ help='Display additional information. Used for debugging '
+ '`powerline-lint\' itself, not for debugging configuration.'
+ )
+ return parser
diff --git a/powerline/commands/main.py b/powerline/commands/main.py
new file mode 100644
index 0000000..373225f
--- /dev/null
+++ b/powerline/commands/main.py
@@ -0,0 +1,190 @@
+# vim:fileencoding=utf-8:noet
+# WARNING: using unicode_literals causes errors in argparse
+from __future__ import (division, absolute_import, print_function)
+
+import argparse
+import sys
+
+from itertools import chain
+
+from powerline.lib.overrides import parsedotval, parse_override_var
+from powerline.lib.dict import mergeargs
+from powerline.lib.encoding import get_preferred_arguments_encoding
+from powerline.lib.unicode import u, unicode
+from powerline.bindings.wm import wm_threads
+
+
+if sys.version_info < (3,):
+ encoding = get_preferred_arguments_encoding()
+
+ def arg_to_unicode(s):
+ return unicode(s, encoding, 'replace') if not isinstance(s, unicode) else s # NOQA
+else:
+ def arg_to_unicode(s):
+ return s
+
+
+def finish_args(parser, environ, args, is_daemon=False):
+ '''Do some final transformations
+
+ Transforms ``*_override`` arguments into dictionaries, adding overrides from
+ environment variables. Transforms ``renderer_arg`` argument into dictionary
+ as well, but only if it is true.
+
+ :param dict environ:
+ Environment from which additional overrides should be taken from.
+ :param args:
+ Arguments object returned by
+ :py:meth:`argparse.ArgumentParser.parse_args`. Will be modified
+ in-place.
+
+ :return: Object received as second (``args``) argument.
+ '''
+ args.config_override = mergeargs(chain(
+ parse_override_var(environ.get('POWERLINE_CONFIG_OVERRIDES', '')),
+ (parsedotval(v) for v in args.config_override or ()),
+ ))
+ args.theme_override = mergeargs(chain(
+ parse_override_var(environ.get('POWERLINE_THEME_OVERRIDES', '')),
+ (parsedotval(v) for v in args.theme_override or ()),
+ ))
+ if args.renderer_arg:
+ args.renderer_arg = mergeargs((parsedotval(v) for v in args.renderer_arg), remove=True)
+ if 'pane_id' in args.renderer_arg:
+ if isinstance(args.renderer_arg['pane_id'], (bytes, unicode)):
+ try:
+ args.renderer_arg['pane_id'] = int(args.renderer_arg['pane_id'].lstrip(' %'))
+ except ValueError:
+ pass
+ if 'client_id' not in args.renderer_arg:
+ args.renderer_arg['client_id'] = args.renderer_arg['pane_id']
+ args.config_path = (
+ [path for path in environ.get('POWERLINE_CONFIG_PATHS', '').split(':') if path]
+ + (args.config_path or [])
+ )
+ if args.ext[0].startswith('wm.'):
+ if not is_daemon:
+ parser.error('WM bindings must be used with daemon only')
+ elif args.ext[0][3:] not in wm_threads:
+ parser.error('WM binding not found')
+ elif not args.side:
+ parser.error('expected one argument')
+ return args
+
+
+def int_or_sig(s):
+ if s.startswith('sig'):
+ return u(s)
+ else:
+ return int(s)
+
+
+def get_argparser(ArgumentParser=argparse.ArgumentParser):
+ parser = ArgumentParser(description='Powerline prompt and statusline script.')
+ parser.add_argument(
+ 'ext', nargs=1,
+ help='Extension: application for which powerline command is launched '
+ '(usually `shell\' or `tmux\'). Also supports `wm.\' extensions: '
+ + ', '.join(('`wm.' + key + '\'' for key in wm_threads.keys())) + '.'
+ )
+ parser.add_argument(
+ 'side', nargs='?', choices=('left', 'right', 'above', 'aboveleft'),
+ help='Side: `left\' and `right\' represent left and right side '
+ 'respectively, `above\' emits lines that are supposed to be printed '
+ 'just above the prompt and `aboveleft\' is like concatenating '
+ '`above\' with `left\' with the exception that only one Python '
+ 'instance is used in this case. May be omitted for `wm.*\' extensions.'
+ )
+ parser.add_argument(
+ '-r', '--renderer-module', metavar='MODULE', type=str,
+ help='Renderer module. Usually something like `.bash\' or `.zsh\' '
+ '(with leading dot) which is `powerline.renderers.{ext}{MODULE}\', '
+ 'may also be full module name (must contain at least one dot or '
+ 'end with a dot in case it is top-level module) or '
+ '`powerline.renderers\' submodule (in case there are no dots).'
+ )
+ parser.add_argument(
+ '-w', '--width', type=int,
+ help='Maximum prompt with. Triggers truncation of some segments.'
+ )
+ parser.add_argument(
+ '--last-exit-code', metavar='INT', type=int_or_sig,
+ help='Last exit code.'
+ )
+ parser.add_argument(
+ '--last-pipe-status', metavar='LIST', default='',
+ type=lambda s: [int_or_sig(status) for status in s.split()],
+ help='Like above, but is supposed to contain space-separated array '
+ 'of statuses, representing exit statuses of commands in one pipe.'
+ )
+ parser.add_argument(
+ '--jobnum', metavar='INT', type=int,
+ help='Number of jobs.'
+ )
+ parser.add_argument(
+ '-c', '--config-override', metavar='KEY.KEY=VALUE', type=arg_to_unicode,
+ action='append',
+ help='Configuration overrides for `config.json\'. Is translated to a '
+ 'dictionary and merged with the dictionary obtained from actual '
+ 'JSON configuration: KEY.KEY=VALUE is translated to '
+ '`{"KEY": {"KEY": VALUE}}\' and then merged recursively. '
+ 'VALUE may be any JSON value, values that are not '
+ '`null\', `true\', `false\', start with digit, `{\', `[\' '
+ 'are treated like strings. If VALUE is omitted '
+ 'then corresponding key is removed.'
+ )
+ parser.add_argument(
+ '-t', '--theme-override', metavar='THEME.KEY.KEY=VALUE', type=arg_to_unicode,
+ action='append',
+ help='Like above, but theme-specific. THEME should point to '
+ 'an existing and used theme to have any effect, but it is fine '
+ 'to use any theme here.'
+ )
+ parser.add_argument(
+ '-R', '--renderer-arg',
+ metavar='KEY=VAL', type=arg_to_unicode, action='append',
+ help='Like above, but provides argument for renderer. Is supposed '
+ 'to be used only by shell bindings to provide various data like '
+ 'last-exit-code or last-pipe-status (they are not using '
+ '`--renderer-arg\' for historical reasons: `--renderer-arg\' '
+ 'was added later).'
+ )
+ parser.add_argument(
+ '-p', '--config-path', action='append', metavar='PATH',
+ help='Path to configuration directory. If it is present then '
+ 'configuration files will only be sought in the provided path. '
+ 'May be provided multiple times to search in a list of directories.'
+ )
+ parser.add_argument(
+ '--socket', metavar='ADDRESS', type=str,
+ help='Socket address to use in daemon clients. Is always UNIX domain '
+ 'socket on linux and file socket on Mac OS X. Not used here, '
+ 'present only for compatibility with other powerline clients. '
+ 'This argument must always be the first one and be in a form '
+ '`--socket ADDRESS\': no `=\' or short form allowed '
+ '(in other powerline clients, not here).'
+ )
+ return parser
+
+
+def write_output(args, powerline, segment_info, write):
+ if args.renderer_arg:
+ segment_info.update(args.renderer_arg)
+ if args.side.startswith('above'):
+ for line in powerline.render_above_lines(
+ width=args.width,
+ segment_info=segment_info,
+ mode=segment_info.get('mode', None),
+ ):
+ if line:
+ write(line + '\n')
+ args.side = args.side[len('above'):]
+
+ if args.side:
+ rendered = powerline.render(
+ width=args.width,
+ side=args.side,
+ segment_info=segment_info,
+ mode=segment_info.get('mode', None),
+ )
+ write(rendered)
diff --git a/powerline/config.py b/powerline/config.py
new file mode 100644
index 0000000..edcf921
--- /dev/null
+++ b/powerline/config.py
@@ -0,0 +1,10 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+
+POWERLINE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BINDINGS_DIRECTORY = os.path.join(POWERLINE_ROOT, 'powerline', 'bindings')
+TMUX_CONFIG_DIRECTORY = os.path.join(BINDINGS_DIRECTORY, 'tmux')
+DEFAULT_SYSTEM_CONFIG_DIR = None
diff --git a/powerline/config_files/colors.json b/powerline/config_files/colors.json
new file mode 100644
index 0000000..1564f18
--- /dev/null
+++ b/powerline/config_files/colors.json
@@ -0,0 +1,124 @@
+{
+ "colors": {
+ "black": 16,
+ "white": 231,
+
+ "green": 2,
+ "darkestgreen": 22,
+ "darkgreen": 28,
+ "mediumgreen": 70,
+ "brightgreen": 148,
+
+ "darkestcyan": 23,
+ "darkcyan": 74,
+ "mediumcyan": 117,
+ "brightcyan": 159,
+
+ "darkestblue": 24,
+ "darkblue": 31,
+
+ "red": 1,
+ "darkestred": 52,
+ "darkred": 88,
+ "mediumred": 124,
+ "brightred": 160,
+ "brightestred": 196,
+
+ "darkestpurple": 55,
+ "mediumpurple": 98,
+ "brightpurple": 189,
+
+ "darkorange": 94,
+ "mediumorange": 166,
+ "brightorange": 208,
+ "brightestorange": 214,
+
+ "yellow": 11,
+ "brightyellow": 220,
+
+ "gray0": 233,
+ "gray1": 235,
+ "gray2": 236,
+ "gray3": 239,
+ "gray4": 240,
+ "gray5": 241,
+ "gray6": 244,
+ "gray7": 245,
+ "gray8": 247,
+ "gray9": 250,
+ "gray10": 252,
+
+ "gray11": 234,
+ "gray90": 254,
+
+ "gray70": [249, "b3b3b3"],
+
+ "lightyellowgreen": 106,
+ "gold3": 178,
+ "orangered": 202,
+
+ "steelblue": 67,
+ "darkorange3": 166,
+ "skyblue1": 117,
+ "khaki1": 228,
+
+ "solarized:base03": [8, "002b36"],
+ "solarized:base02": [0, "073642"],
+ "solarized:base01": [10, "586e75"],
+ "solarized:base00": [11, "657b83"],
+ "solarized:base0": [12, "839496"],
+ "solarized:base1": [14, "93a1a1"],
+ "solarized:base2": [7, "eee8d5"],
+ "solarized:base3": [15, "fdf6e3"],
+ "solarized:yellow": [3, "b58900"],
+ "solarized:orange": [9, "cb4b16"],
+ "solarized:red": [1, "dc322f"],
+ "solarized:magenta": [5, "d33682"],
+ "solarized:violet": [13, "6c71c4"],
+ "solarized:blue": [4, "268bd2"],
+ "solarized:cyan": [6, "2aa198"],
+ "solarized:green": [2, "859900"]
+ },
+ "gradients": {
+ "dark_GREEN_Orange_red": [
+ [22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 94, 94, 94, 94, 94, 94, 94, 88, 52],
+ ["006000", "006000", "006000", "006000", "006000", "006000", "006000", "006000", "006000", "036000", "076000", "0a6000", "0d6000", "106000", "126000", "146000", "166000", "186000", "1a6000", "1b6000", "1d6000", "1e6000", "206000", "216000", "236000", "246000", "256000", "266000", "286000", "296000", "2a6000", "2b6000", "2c6100", "2d6100", "2f6100", "306100", "316100", "326100", "336100", "346100", "356100", "366100", "376100", "386100", "386100", "396100", "3a6100", "3b6100", "3c6100", "3d6100", "3e6100", "3f6100", "406100", "406100", "416100", "426000", "436000", "446000", "456000", "456000", "466000", "476000", "486000", "496000", "496000", "4a6000", "4b6000", "4c6000", "4d6000", "4d6000", "4e6000", "4f6000", "506000", "506000", "516000", "526000", "536000", "536000", "546000", "556000", "566000", "566000", "576000", "586000", "596000", "596000", "5a6000", "5d6000", "616000", "646000", "686000", "6b6000", "6f6000", "726000", "766000", "796000", "7d6000", "806000", "7e5500", "6f3105", "5d0001"]
+ ],
+ "GREEN_Orange_red": [
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1],
+ ["005f00", "015f00", "025f00", "035f00", "045f00", "055f00", "065f00", "075f00", "085f00", "095f00", "0b5f00", "0c5f00", "0d5f00", "0e5f00", "0f5f00", "105f00", "115f00", "125f00", "135f00", "145f00", "165f00", "175f00", "185f00", "195f00", "1a5f00", "1b5f00", "1c5f00", "1d5f00", "1e5f00", "1f5f00", "215f00", "225f00", "235f00", "245f00", "255f00", "265f00", "275f00", "285f00", "295f00", "2a5f00", "2c5f00", "2d5f00", "2e5f00", "2f5f00", "305f00", "315f00", "325f00", "335f00", "345f00", "355f00", "375f00", "385f00", "395f00", "3a5f00", "3b5f00", "3c5f00", "3d5f00", "3e5f00", "3f5f00", "415f00", "425f00", "435f00", "445f00", "455f00", "465f00", "475f00", "485f00", "495f00", "4a5f00", "4c5f00", "4d5f00", "4e5f00", "4f5f00", "505f00", "515f00", "525f00", "535f00", "545f00", "555f00", "575f00", "585f00", "595f00", "5a5f00", "5b5f00", "5c5f00", "5d5f00", "5e5f00", "615f00", "655f00", "685f00", "6c5f00", "6f5f00", "735f00", "765f00", "7a5f00", "7d5f00", "815f00", "845f00", "815200", "702900"]
+ ],
+ "green_yellow_red": [
+ [190, 184, 178, 172, 166, 160],
+ ["8ae71c", "8ce71c", "8fe71c", "92e71c", "95e71d", "98e71d", "9ae71d", "9de71d", "a0e71e", "a3e71e", "a6e71e", "a8e71e", "abe71f", "aee71f", "b1e71f", "b4e71f", "b6e720", "b9e720", "bce720", "bfe720", "c2e821", "c3e721", "c5e621", "c7e521", "c9e522", "cbe422", "cde322", "cfe222", "d1e223", "d3e123", "d5e023", "d7df23", "d9df24", "dbde24", "dddd24", "dfdc24", "e1dc25", "e3db25", "e5da25", "e7d925", "e9d926", "e9d626", "e9d426", "e9d126", "e9cf27", "e9cc27", "e9ca27", "e9c727", "e9c528", "e9c228", "e9c028", "e9bd28", "e9bb29", "e9b829", "e9b629", "e9b329", "e9b12a", "e9ae2a", "e9ac2a", "e9a92a", "eaa72b", "eaa42b", "eaa22b", "ea9f2b", "ea9d2c", "ea9b2c", "ea982c", "ea962c", "ea942d", "ea912d", "ea8f2d", "ea8d2d", "ea8a2e", "ea882e", "ea862e", "ea832e", "ea812f", "ea7f2f", "ea7c2f", "ea7a2f", "eb7830", "eb7530", "eb7330", "eb7130", "eb6f31", "eb6c31", "eb6a31", "eb6831", "eb6632", "eb6332", "eb6132", "eb5f32", "eb5d33", "eb5a33", "eb5833", "eb5633", "eb5434", "eb5134", "eb4f34", "eb4d34", "ec4b35"]
+ ],
+ "green_yellow_orange_red": [
+ [2, 3, 9, 1],
+ ["719e07", "739d06", "759c06", "779c06", "799b06", "7b9a05", "7d9a05", "7f9905", "819805", "839805", "859704", "879704", "899604", "8b9504", "8d9504", "8f9403", "919303", "949303", "969203", "989102", "9a9102", "9c9002", "9e9002", "a08f02", "a28e01", "a48e01", "a68d01", "a88c01", "aa8c01", "ac8b00", "ae8a00", "b08a00", "b28900", "b58900", "b58700", "b68501", "b78302", "b78102", "b87f03", "b97d04", "b97b04", "ba7905", "bb7806", "bb7606", "bc7407", "bd7208", "bd7008", "be6e09", "bf6c0a", "bf6a0a", "c0690b", "c1670c", "c1650c", "c2630d", "c3610e", "c35f0e", "c45d0f", "c55b10", "c55a10", "c65811", "c75612", "c75412", "c85213", "c95014", "c94e14", "ca4c15", "cb4b16", "cb4a16", "cc4917", "cc4818", "cd4719", "cd4719", "ce461a", "ce451b", "cf441c", "cf441c", "d0431d", "d0421e", "d1411f", "d1411f", "d24020", "d23f21", "d33e22", "d33e22", "d43d23", "d43c24", "d53b25", "d53b25", "d63a26", "d63927", "d73828", "d73828", "d83729", "d8362a", "d9352b", "d9352b", "da342c", "da332d", "db322e", "dc322f"]
+ ],
+ "yellow_red": [
+ [220, 178, 172, 166, 160],
+ ["ffd700", "fdd500", "fbd300", "fad200", "f8d000", "f7cf00", "f5cd00", "f3cb00", "f2ca00", "f0c800", "efc700", "edc500", "ebc300", "eac200", "e8c000", "e7bf00", "e5bd00", "e3bb00", "e2ba00", "e0b800", "dfb700", "ddb500", "dbb300", "dab200", "d8b000", "d7af00", "d7ad00", "d7ab00", "d7aa00", "d7a800", "d7a700", "d7a500", "d7a300", "d7a200", "d7a000", "d79f00", "d79d00", "d79b00", "d79a00", "d79800", "d79700", "d79500", "d79300", "d79200", "d79000", "d78f00", "d78d00", "d78b00", "d78a00", "d78800", "d78700", "d78500", "d78300", "d78200", "d78000", "d77f00", "d77d00", "d77b00", "d77a00", "d77800", "d77700", "d77500", "d77300", "d77200", "d77000", "d76f00", "d76d00", "d76b00", "d76a00", "d76800", "d76700", "d76500", "d76300", "d76200", "d76000", "d75f00", "d75b00", "d75700", "d75300", "d74f00", "d74c00", "d74800", "d74400", "d74000", "d73c00", "d73900", "d73500", "d73100", "d72d00", "d72900", "d72600", "d72200", "d71e00", "d71a00", "d71600", "d71300", "d70f00", "d70b00", "d70700"]
+ ],
+ "yellow_orange_red": [
+ [3, 9, 1],
+ ["b58900", "b58700", "b58600", "b68501", "b68401", "b78202", "b78102", "b88003", "b87f03", "b87d03", "b97c04", "b97b04", "ba7a05", "ba7805", "bb7706", "bb7606", "bc7507", "bc7307", "bc7207", "bd7108", "bd7008", "be6e09", "be6d09", "bf6c0a", "bf6b0a", "c06a0b", "c0680b", "c0670b", "c1660c", "c1650c", "c2630d", "c2620d", "c3610e", "c3600e", "c35e0e", "c45d0f", "c45c0f", "c55b10", "c55910", "c65811", "c65711", "c75612", "c75412", "c75312", "c85213", "c85113", "c94f14", "c94e14", "ca4d15", "ca4c15", "cb4b16", "cb4a16", "cb4a17", "cc4917", "cc4918", "cc4818", "cd4819", "cd4719", "cd471a", "ce461a", "ce461b", "ce451b", "cf451c", "cf441c", "cf441d", "d0431d", "d0431e", "d0421e", "d1421f", "d1411f", "d14120", "d24020", "d24021", "d23f21", "d33f22", "d33e22", "d33e23", "d43d23", "d43d24", "d43c24", "d53c25", "d53b25", "d53b26", "d63a26", "d63a27", "d63927", "d73928", "d73828", "d73829", "d83729", "d8372a", "d8362a", "d9362b", "d9352b", "d9352c", "da342c", "da342d", "da332d", "db332e"]
+ ],
+ "blue_red": [
+ [39, 74, 68, 67, 103, 97, 96, 132, 131, 167, 203, 197],
+ ["19b4fe", "1bb2fc", "1db1fa", "1faff8", "22aef6", "24adf4", "26abf2", "29aaf0", "2ba9ee", "2da7ec", "30a6ea", "32a5e8", "34a3e6", "36a2e4", "39a0e2", "3b9fe1", "3d9edf", "409cdd", "429bdb", "449ad9", "4798d7", "4997d5", "4b96d3", "4d94d1", "5093cf", "5292cd", "5490cb", "578fc9", "598dc7", "5b8cc6", "5e8bc4", "6089c2", "6288c0", "6487be", "6785bc", "6984ba", "6b83b8", "6e81b6", "7080b4", "727eb2", "757db0", "777cae", "797aac", "7b79ab", "7e78a9", "8076a7", "8275a5", "8574a3", "8772a1", "89719f", "8c709d", "8e6e9b", "906d99", "926b97", "956a95", "976993", "996791", "9c668f", "9e658e", "a0638c", "a3628a", "a56188", "a75f86", "a95e84", "ac5c82", "ae5b80", "b05a7e", "b3587c", "b5577a", "b75678", "ba5476", "bc5374", "be5273", "c05071", "c34f6f", "c54e6d", "c74c6b", "ca4b69", "cc4967", "ce4865", "d14763", "d34561", "d5445f", "d7435d", "da415b", "dc4059", "de3f58", "e13d56", "e33c54", "e53a52", "e83950", "ea384e", "ec364c", "ee354a", "f13448", "f33246", "f53144", "f83042", "fa2e40"]
+ ],
+ "white_red": [
+ [231, 255, 223, 216, 209, 202, 196],
+ ["ffffff", "fefefe", "fdfdfd", "fdfdfd", "fcfcfc", "fbfbfb", "fafafa", "fafafa", "f9f9f9", "f8f8f8", "f7f7f7", "f7f7f7", "f6f6f6", "f5f5f5", "f4f4f4", "f4f3f4", "f3f3f3", "f2f2f2", "f1f1f1", "f0f0f0", "f0f0f0", "efefef", "eeeeee", "efecea", "f1eae4", "f2e8de", "f3e6d8", "f5e4d3", "f6e2cd", "f7e0c7", "f8dec2", "f9dcbc", "fadab6", "fad8b1", "fbd5ac", "fbd2a9", "fbcea5", "fbcaa1", "fbc79e", "fbc39a", "fbc097", "fbbc93", "fbb88f", "fbb58c", "fab188", "faad85", "faaa81", "fba67e", "fba37a", "fb9f76", "fb9c73", "fb986f", "fb946c", "fb9168", "fa8d65", "fa8961", "fa865c", "fa8256", "fb7f4f", "fb7b48", "fb7841", "fb743a", "fb7133", "fb6d2c", "fa6a23", "fa661a", "fa620e", "fa5f03", "fa5d03", "fa5b03", "fa5a03", "fa5803", "fa5703", "fa5503", "fa5303", "fa5103", "fa4f03", "fa4e03", "fa4c03", "fa4a04", "fa4804", "fa4604", "fa4404", "fa4204", "fa3f04", "fa3d04", "fa3b04", "fa3805", "fa3605", "fa3305", "fb3105", "fb2e05", "fb2a05", "fb2705", "fb2306", "fb1f06", "fb1b06", "fb1506", "fb0e06", "fa0506", "fa0007"]
+ ],
+ "dark_green_gray": [
+ [70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247],
+ ["51b000", "52b000", "54b000", "55b002", "56b007", "57b00d", "58b011", "59af15", "5aaf18", "5caf1b", "5daf1e", "5eaf21", "5faf23", "60ae25", "61ae27", "62ae2a", "63ae2c", "64ae2e", "65ae30", "66ae31", "67ad33", "68ad35", "69ad37", "69ad38", "6aad3a", "6bad3c", "6cac3d", "6dac3f", "6eac40", "6fac42", "70ac44", "70ac45", "71ab47", "72ab48", "73ab49", "74ab4b", "75ab4c", "75ab4e", "76aa4f", "77aa51", "78aa52", "79aa53", "79aa55", "7aaa56", "7ba957", "7ca959", "7ca95a", "7da95b", "7ea95d", "7fa95e", "7fa85f", "80a861", "81a862", "81a863", "82a865", "83a766", "83a767", "84a768", "85a76a", "85a76b", "86a66c", "87a66d", "87a66f", "88a670", "89a671", "89a672", "8aa574", "8ba575", "8ba576", "8ca577", "8da579", "8da47a", "8ea47b", "8ea47c", "8fa47d", "90a47f", "90a380", "91a381", "91a382", "92a384", "93a385", "93a286", "94a287", "94a288", "95a28a", "95a18b", "96a18c", "97a18d", "97a18e", "98a190", "98a091", "99a092", "99a093", "9aa094", "9aa096", "9b9f97", "9b9f98", "9c9f99", "9c9f9a", "9d9e9c", "9d9e9d"]
+ ],
+ "light_green_gray": [
+ [148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 187, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250],
+ ["a3d900", "a4d800", "a4d800", "a5d805", "a5d80d", "a6d714", "a6d719", "a6d71d", "a7d621", "a7d625", "a8d628", "a8d62b", "a8d52e", "a9d531", "a9d533", "aad536", "aad438", "aad43a", "abd43d", "abd33f", "abd341", "acd343", "acd345", "acd247", "add249", "add24b", "add14d", "aed14f", "aed151", "aed152", "afd054", "afd056", "afd058", "b0d059", "b0cf5b", "b0cf5d", "b1cf5e", "b1ce60", "b1ce62", "b1ce63", "b2ce65", "b2cd67", "b2cd68", "b3cd6a", "b3cc6b", "b3cc6d", "b3cc6e", "b4cc70", "b4cb71", "b4cb73", "b4cb75", "b5ca76", "b5ca78", "b5ca79", "b5ca7a", "b6c97c", "b6c97d", "b6c97f", "b6c880", "b6c882", "b7c883", "b7c885", "b7c786", "b7c788", "b7c789", "b8c68a", "b8c68c", "b8c68d", "b8c68f", "b8c590", "b9c591", "b9c593", "b9c494", "b9c496", "b9c497", "b9c498", "bac39a", "bac39b", "bac39d", "bac29e", "bac29f", "bac2a1", "bac2a2", "bac1a4", "bbc1a5", "bbc1a6", "bbc0a8", "bbc0a9", "bbc0aa", "bbc0ac", "bbbfad", "bbbfae", "bbbfb0", "bbbeb1", "bcbeb3", "bcbeb4", "bcbdb5", "bcbdb7", "bcbdb8", "bcbdb9", "bcbcbb"]
+ ]
+ }
+}
diff --git a/powerline/config_files/colorschemes/default.json b/powerline/config_files/colorschemes/default.json
new file mode 100644
index 0000000..767e72e
--- /dev/null
+++ b/powerline/config_files/colorschemes/default.json
@@ -0,0 +1,57 @@
+{
+ "name": "Default",
+ "groups": {
+ "information:additional": { "fg": "gray9", "bg": "gray4", "attrs": [] },
+ "information:regular": { "fg": "gray10", "bg": "gray4", "attrs": ["bold"] },
+ "information:highlighted": { "fg": "white", "bg": "gray4", "attrs": [] },
+ "information:priority": { "fg": "brightyellow", "bg": "mediumorange", "attrs": [] },
+ "warning:regular": { "fg": "white", "bg": "brightred", "attrs": ["bold"] },
+ "critical:failure": { "fg": "white", "bg": "darkestred", "attrs": [] },
+ "critical:success": { "fg": "white", "bg": "darkestgreen", "attrs": [] },
+ "background": { "fg": "white", "bg": "gray0", "attrs": [] },
+ "background:divider": { "fg": "gray5", "bg": "gray0", "attrs": [] },
+ "session": { "fg": "black", "bg": "gray10", "attrs": ["bold"] },
+ "date": { "fg": "gray8", "bg": "gray2", "attrs": [] },
+ "time": { "fg": "gray10", "bg": "gray2", "attrs": ["bold"] },
+ "time:divider": { "fg": "gray5", "bg": "gray2", "attrs": [] },
+ "email_alert": "warning:regular",
+ "email_alert_gradient": { "fg": "white", "bg": "yellow_orange_red", "attrs": ["bold"] },
+ "hostname": { "fg": "black", "bg": "gray10", "attrs": ["bold"] },
+ "weather": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "weather_temp_gradient": { "fg": "blue_red", "bg": "gray0", "attrs": [] },
+ "weather_condition_hot": { "fg": "khaki1", "bg": "gray0", "attrs": [] },
+ "weather_condition_snowy": { "fg": "skyblue1", "bg": "gray0", "attrs": [] },
+ "weather_condition_rainy": { "fg": "skyblue1", "bg": "gray0", "attrs": [] },
+ "uptime": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "external_ip": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "internal_ip": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "network_load": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "network_load_gradient": { "fg": "green_yellow_orange_red", "bg": "gray0", "attrs": [] },
+ "network_load_sent_gradient": "network_load_gradient",
+ "network_load_recv_gradient": "network_load_gradient",
+ "network_load:divider": "background:divider",
+ "system_load": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "system_load_gradient": { "fg": "green_yellow_orange_red", "bg": "gray0", "attrs": [] },
+ "environment": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "cpu_load_percent": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "cpu_load_percent_gradient": { "fg": "green_yellow_orange_red", "bg": "gray0", "attrs": [] },
+ "battery": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "battery_gradient": { "fg": "white_red", "bg": "gray0", "attrs": [] },
+ "battery_full": { "fg": "red", "bg": "gray0", "attrs": [] },
+ "battery_empty": { "fg": "white", "bg": "gray0", "attrs": [] },
+ "player": { "fg": "gray10", "bg": "black", "attrs": [] },
+ "user": { "fg": "white", "bg": "darkblue", "attrs": ["bold"] },
+ "branch": { "fg": "gray9", "bg": "gray2", "attrs": [] },
+ "branch_dirty": { "fg": "brightyellow", "bg": "gray2", "attrs": [] },
+ "branch_clean": { "fg": "gray9", "bg": "gray2", "attrs": [] },
+ "branch:divider": { "fg": "gray7", "bg": "gray2", "attrs": [] },
+ "stash": "branch_dirty",
+ "stash:divider": "branch:divider",
+ "cwd": "information:additional",
+ "cwd:current_folder": "information:regular",
+ "cwd:divider": { "fg": "gray7", "bg": "gray4", "attrs": [] },
+ "virtualenv": { "fg": "white", "bg": "darkcyan", "attrs": [] },
+ "attached_clients": { "fg": "gray8", "bg": "gray0", "attrs": [] },
+ "workspace": "information:regular"
+ }
+}
diff --git a/powerline/config_files/colorschemes/ipython/__main__.json b/powerline/config_files/colorschemes/ipython/__main__.json
new file mode 100644
index 0000000..982ea35
--- /dev/null
+++ b/powerline/config_files/colorschemes/ipython/__main__.json
@@ -0,0 +1,6 @@
+{
+ "groups": {
+ "prompt": "information:additional",
+ "prompt_count": "information:highlighted"
+ }
+}
diff --git a/powerline/config_files/colorschemes/pdb/__main__.json b/powerline/config_files/colorschemes/pdb/__main__.json
new file mode 100644
index 0000000..01a51fe
--- /dev/null
+++ b/powerline/config_files/colorschemes/pdb/__main__.json
@@ -0,0 +1,8 @@
+{
+ "groups": {
+ "current_code_name": "information:additional",
+ "current_context": "current_code_name",
+ "current_line": "information:regular",
+ "current_file": "information:regular"
+ }
+}
diff --git a/powerline/config_files/colorschemes/pdb/default.json b/powerline/config_files/colorschemes/pdb/default.json
new file mode 100644
index 0000000..b97acf7
--- /dev/null
+++ b/powerline/config_files/colorschemes/pdb/default.json
@@ -0,0 +1,5 @@
+{
+ "groups": {
+ "stack_depth": { "fg": "gray1", "bg": "gray10", "attrs": ["bold"] }
+ }
+}
diff --git a/powerline/config_files/colorschemes/pdb/solarized.json b/powerline/config_files/colorschemes/pdb/solarized.json
new file mode 100644
index 0000000..2e1c787
--- /dev/null
+++ b/powerline/config_files/colorschemes/pdb/solarized.json
@@ -0,0 +1,5 @@
+{
+ "groups": {
+ "stack_depth": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] }
+ }
+}
diff --git a/powerline/config_files/colorschemes/shell/__main__.json b/powerline/config_files/colorschemes/shell/__main__.json
new file mode 100644
index 0000000..6e3856f
--- /dev/null
+++ b/powerline/config_files/colorschemes/shell/__main__.json
@@ -0,0 +1,10 @@
+{
+ "groups": {
+ "continuation": "cwd",
+ "continuation:current": "cwd:current_folder",
+ "exit_fail": "critical:failure",
+ "exit_success": "critical:success",
+ "jobnum": "information:priority",
+ "superuser": "warning:regular"
+ }
+}
diff --git a/powerline/config_files/colorschemes/shell/default.json b/powerline/config_files/colorschemes/shell/default.json
new file mode 100644
index 0000000..1126feb
--- /dev/null
+++ b/powerline/config_files/colorschemes/shell/default.json
@@ -0,0 +1,16 @@
+{
+ "name": "Default color scheme for shell prompts",
+ "groups": {
+ "hostname": { "fg": "brightyellow", "bg": "mediumorange", "attrs": [] },
+ "environment": { "fg": "white", "bg": "darkestgreen", "attrs": [] },
+ "mode": { "fg": "darkestgreen", "bg": "brightgreen", "attrs": ["bold"] },
+ "attached_clients": { "fg": "white", "bg": "darkestgreen", "attrs": [] }
+ },
+ "mode_translations": {
+ "vicmd": {
+ "groups": {
+ "mode": {"fg": "darkestcyan", "bg": "white", "attrs": ["bold"]}
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/colorschemes/shell/solarized.json b/powerline/config_files/colorschemes/shell/solarized.json
new file mode 100644
index 0000000..69dcab1
--- /dev/null
+++ b/powerline/config_files/colorschemes/shell/solarized.json
@@ -0,0 +1,13 @@
+{
+ "name": "Solarized dark for shell",
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": ["bold"] }
+ },
+ "mode_translations": {
+ "vicmd": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/colorschemes/solarized.json b/powerline/config_files/colorschemes/solarized.json
new file mode 100644
index 0000000..7951f0b
--- /dev/null
+++ b/powerline/config_files/colorschemes/solarized.json
@@ -0,0 +1,40 @@
+{
+ "name": "Solarized dark",
+ "groups": {
+ "information:additional": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] },
+ "information:regular": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] },
+ "information:highlighted": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"]},
+ "information:priority": { "fg": "solarized:base3", "bg": "solarized:yellow", "attrs": [] },
+ "warning:regular": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": [] },
+ "critical:failure": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": [] },
+ "critical:success": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] },
+ "background": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": [] },
+ "background:divider": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] },
+ "user": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] },
+ "virtualenv": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] },
+ "branch": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": [] },
+ "branch_clean": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "stash": "branch_dirty",
+ "email_alert_gradient": { "fg": "solarized:base3", "bg": "yellow_orange_red", "attrs": [] },
+ "email_alert": "warning:regular",
+ "cwd": "information:additional",
+ "cwd:current_folder": "information:regular",
+ "cwd:divider": { "fg": "solarized:base1", "bg": "solarized:base01", "attrs": [] },
+ "network_load": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] },
+ "network_load:divider": "network_load",
+ "network_load_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base03", "attrs": [] },
+ "network_load_sent_gradient": "network_load_gradient",
+ "network_load_recv_gradient": "network_load_gradient",
+ "hostname": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "environment": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] },
+ "attached_clients": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": [] },
+ "date": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "time": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": ["bold"] },
+ "time:divider": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "system_load": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] },
+ "weather_temp_gradient": { "fg": "blue_red", "bg": "solarized:base03", "attrs": [] },
+ "weather": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] },
+ "uptime": { "fg": "solarized:base1", "bg": "solarized:base03", "attrs": [] }
+ }
+}
diff --git a/powerline/config_files/colorschemes/tmux/default.json b/powerline/config_files/colorschemes/tmux/default.json
new file mode 100644
index 0000000..8525fb0
--- /dev/null
+++ b/powerline/config_files/colorschemes/tmux/default.json
@@ -0,0 +1,14 @@
+{
+ "groups": {
+ "active_window_status": {"fg": "darkblue", "bg": "gray0", "attrs": []},
+ "window_status": {"fg": "gray70", "bg": "gray0", "attrs": []},
+ "activity_status": {"fg": "yellow", "bg": "gray0", "attrs": []},
+ "bell_status": {"fg": "red", "bg": "gray0", "attrs": []},
+ "window": {"fg": "gray6", "bg": "gray0", "attrs": []},
+ "window:divider": {"fg": "gray4", "bg": "gray0", "attrs": []},
+ "window:current": {"fg": "mediumcyan", "bg": "darkblue", "attrs": []},
+ "window_name": {"fg": "white", "bg": "darkblue", "attrs": ["bold"]},
+ "session": {"fg": "black", "bg": "gray90", "attrs": ["bold"]},
+ "session:prefix": {"fg": "gray90", "bg": "darkblue", "attrs": ["bold"]}
+ }
+}
diff --git a/powerline/config_files/colorschemes/tmux/solarized.json b/powerline/config_files/colorschemes/tmux/solarized.json
new file mode 100644
index 0000000..20c42d3
--- /dev/null
+++ b/powerline/config_files/colorschemes/tmux/solarized.json
@@ -0,0 +1,14 @@
+{
+ "groups": {
+ "active_window_status": { "fg": "solarized:blue", "bg": "solarized:base02", "attrs": [] },
+ "window_status": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "activity_status": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": [] },
+ "bell_status": { "fg": "solarized:red", "bg": "solarized:base02", "attrs": [] },
+ "window": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "window:divider": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": [] },
+ "window:current": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "window_name": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] },
+ "session": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "session:prefix": { "fg": "solarized:base01", "bg": "solarized:base3", "attrs": [] }
+ }
+}
diff --git a/powerline/config_files/colorschemes/vim/__main__.json b/powerline/config_files/colorschemes/vim/__main__.json
new file mode 100644
index 0000000..4f2a817
--- /dev/null
+++ b/powerline/config_files/colorschemes/vim/__main__.json
@@ -0,0 +1,51 @@
+{
+ "groups": {
+ "branch_clean": "branch",
+ "environment": "information:unimportant",
+ "file_size": "information:unimportant",
+ "file_format": "information:unimportant",
+ "file_encoding": "file_format",
+ "file_bom": "file_format",
+ "file_type": "file_format",
+ "branch": "information:additional",
+ "file_scheme": "file_name",
+ "file_directory": "information:additional",
+ "file_name_empty": "file_directory",
+ "line_percent": "information:additional",
+ "line_count": "line_current",
+ "position": "information:additional",
+ "single_tab": "line_current",
+ "many_tabs": "line_current",
+ "bufnr": "file_directory",
+ "winnr": "information:unimportant",
+ "tabnr": "file_directory",
+ "capslock_indicator": "paste_indicator",
+
+ "csv:column_number": "line_current",
+ "csv:column_name": "line_current_symbol",
+
+ "tab:background": "background",
+ "tab:divider": "background:divider",
+
+ "tab_nc:modified_indicator": "modified_indicator",
+ "tab_nc:file_directory": "information:unimportant",
+ "tab_nc:file_name": "tab_nc:file_directory",
+ "tab_nc:tabnr": "tab_nc:file_directory",
+
+ "buf_nc:file_directory": "tab_nc:file_directory",
+ "buf_nc:file_name": "buf_nc:file_directory",
+ "buf_nc:bufnr": "buf_nc:file_directory",
+ "buf_nc:modified_indicator": "tab_nc:modified_indicator",
+
+ "buf_nc_mod:file_directory": "tab_nc:file_directory",
+ "buf_nc_mod:file_name": "buf_nc_mod:file_directory",
+ "buf_nc_mod:bufnr": "buf_nc_mod:file_directory",
+ "buf_nc_mod:modified_indicator": "tab_nc:modified_indicator",
+
+
+ "commandt:label": "file_name",
+ "commandt:background": "background",
+ "commandt:finder": "file_name",
+ "commandt:path": "file_directory"
+ }
+}
diff --git a/powerline/config_files/colorschemes/vim/default.json b/powerline/config_files/colorschemes/vim/default.json
new file mode 100644
index 0000000..e02a160
--- /dev/null
+++ b/powerline/config_files/colorschemes/vim/default.json
@@ -0,0 +1,154 @@
+{
+ "name": "Default color scheme",
+ "groups": {
+ "information:unimportant": { "fg": "gray8", "bg": "gray2", "attrs": [] },
+ "information:additional": { "fg": "gray9", "bg": "gray4", "attrs": [] },
+ "background": { "fg": "white", "bg": "gray2", "attrs": [] },
+ "background:divider": { "fg": "gray6", "bg": "gray2", "attrs": [] },
+ "mode": { "fg": "darkestgreen", "bg": "brightgreen", "attrs": ["bold"] },
+ "visual_range": { "fg": "brightestorange", "bg": "darkorange", "attrs": ["bold"] },
+ "modified_indicator": { "fg": "brightyellow", "bg": "gray4", "attrs": ["bold"] },
+ "paste_indicator": { "fg": "white", "bg": "mediumorange", "attrs": ["bold"] },
+ "readonly_indicator": { "fg": "brightestred", "bg": "gray4", "attrs": [] },
+ "branch_dirty": { "fg": "brightyellow", "bg": "gray4", "attrs": [] },
+ "branch:divider": { "fg": "gray7", "bg": "gray4", "attrs": [] },
+ "file_name": { "fg": "white", "bg": "gray4", "attrs": ["bold"] },
+ "window_title": { "fg": "white", "bg": "gray4", "attrs": [] },
+ "file_name_no_file": { "fg": "gray9", "bg": "gray4", "attrs": ["bold"] },
+ "file_vcs_status": { "fg": "brightestred", "bg": "gray4", "attrs": [] },
+ "file_vcs_status_M": { "fg": "brightyellow", "bg": "gray4", "attrs": [] },
+ "file_vcs_status_A": { "fg": "brightgreen", "bg": "gray4", "attrs": [] },
+ "line_percent": { "fg": "gray9", "bg": "gray4", "attrs": [] },
+ "line_percent_gradient": { "fg": "dark_green_gray", "bg": "gray4", "attrs": [] },
+ "position": { "fg": "gray9", "bg": "gray4", "attrs": [] },
+ "position_gradient": { "fg": "green_yellow_red", "bg": "gray4", "attrs": [] },
+ "line_current": { "fg": "gray1", "bg": "gray10", "attrs": ["bold"] },
+ "line_current_symbol": { "fg": "gray1", "bg": "gray10", "attrs": [] },
+ "virtcol_current_gradient": { "fg": "dark_GREEN_Orange_red", "bg": "gray10", "attrs": [] },
+ "col_current": { "fg": "gray6", "bg": "gray10", "attrs": [] },
+ "modified_buffers": { "fg": "brightyellow", "bg": "gray2", "attrs": [] },
+ "attached_clients": { "fg": "gray8", "bg": "gray2", "attrs": [] },
+ "error": { "fg": "brightestred", "bg": "darkred", "attrs": ["bold"] },
+ "warning": { "fg": "brightyellow", "bg": "darkorange", "attrs": ["bold"] },
+ "current_tag": { "fg": "gray9", "bg": "gray2", "attrs": [] },
+
+ "tab_nc:modified_indicator": { "fg": "brightyellow", "bg": "gray2", "attrs": ["bold"] }
+ },
+ "mode_translations": {
+ "nc": {
+ "colors": {
+ "brightyellow": "darkorange",
+ "brightestred": "darkred",
+ "gray0": "gray0",
+ "gray1": "gray0",
+ "gray2": "gray0",
+ "gray3": "gray1",
+ "gray4": "gray1",
+ "gray5": "gray1",
+ "gray6": "gray1",
+ "gray7": "gray4",
+ "gray8": "gray4",
+ "gray9": "gray4",
+ "gray10": "gray5",
+ "white": "gray6",
+ "dark_green_gray": "gray5"
+ }
+ },
+ "i": {
+ "colors": {
+ "gray0": "darkestblue",
+ "gray1": "darkestblue",
+ "gray2": "darkestblue",
+ "gray3": "darkblue",
+ "gray4": "darkblue",
+ "gray5": "darkestcyan",
+ "gray6": "darkestcyan",
+ "gray7": "darkestcyan",
+ "gray8": "mediumcyan",
+ "gray9": "mediumcyan",
+ "gray10": "mediumcyan",
+ "green_yellow_red": "gray5",
+ "dark_green_gray": "light_green_gray"
+ },
+ "groups": {
+ "mode": { "fg": "darkestcyan", "bg": "white", "attrs": ["bold"] },
+ "background:divider": { "fg": "darkcyan", "bg": "darkestblue", "attrs": [] },
+ "branch:divider": { "fg": "darkcyan", "bg": "darkblue", "attrs": [] }
+ }
+ },
+ "ic": {
+ "colors": {
+ "gray0": "darkestblue",
+ "gray1": "darkestblue",
+ "gray2": "darkestblue",
+ "gray3": "darkblue",
+ "gray4": "darkblue",
+ "gray5": "darkestcyan",
+ "gray6": "darkestcyan",
+ "gray7": "darkestcyan",
+ "gray8": "mediumcyan",
+ "gray9": "mediumcyan",
+ "gray10": "mediumcyan",
+ "green_yellow_red": "gray5",
+ "dark_green_gray": "light_green_gray"
+ },
+ "groups": {
+ "mode": { "fg": "darkestcyan", "bg": "white", "attrs": ["bold"] },
+ "background:divider": { "fg": "darkcyan", "bg": "darkestblue", "attrs": [] },
+ "branch:divider": { "fg": "darkcyan", "bg": "darkblue", "attrs": [] }
+ }
+ },
+ "ix": {
+ "colors": {
+ "gray0": "darkestblue",
+ "gray1": "darkestblue",
+ "gray2": "darkestblue",
+ "gray3": "darkblue",
+ "gray4": "darkblue",
+ "gray5": "darkestcyan",
+ "gray6": "darkestcyan",
+ "gray7": "darkestcyan",
+ "gray8": "mediumcyan",
+ "gray9": "mediumcyan",
+ "gray10": "mediumcyan",
+ "green_yellow_red": "gray5",
+ "dark_green_gray": "light_green_gray"
+ },
+ "groups": {
+ "mode": { "fg": "darkestcyan", "bg": "white", "attrs": ["bold"] },
+ "background:divider": { "fg": "darkcyan", "bg": "darkestblue", "attrs": [] },
+ "branch:divider": { "fg": "darkcyan", "bg": "darkblue", "attrs": [] }
+ }
+ },
+ "v": {
+ "groups": {
+ "mode": { "fg": "darkorange", "bg": "brightestorange", "attrs": ["bold"] }
+ }
+ },
+ "V": {
+ "groups": {
+ "mode": { "fg": "darkorange", "bg": "brightestorange", "attrs": ["bold"] }
+ }
+ },
+ "^V": {
+ "groups": {
+ "mode": { "fg": "darkorange", "bg": "brightestorange", "attrs": ["bold"] }
+ }
+ },
+ "R": {
+ "groups": {
+ "mode": { "fg": "white", "bg": "brightred", "attrs": ["bold"] }
+ }
+ },
+ "Rc": {
+ "groups": {
+ "mode": { "fg": "white", "bg": "brightred", "attrs": ["bold"] }
+ }
+ },
+ "Rx": {
+ "groups": {
+ "mode": { "fg": "white", "bg": "brightred", "attrs": ["bold"] }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/colorschemes/vim/solarized.json b/powerline/config_files/colorschemes/vim/solarized.json
new file mode 100644
index 0000000..55cfaa7
--- /dev/null
+++ b/powerline/config_files/colorschemes/vim/solarized.json
@@ -0,0 +1,121 @@
+{
+ "name": "Solarized dark for vim",
+ "groups": {
+ "information:additional": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] },
+ "information:unimportant": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "background": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": [] },
+ "background:divider": { "fg": "solarized:base00", "bg": "solarized:base02", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": ["bold"] },
+ "visual_range": { "fg": "solarized:green", "bg": "solarized:base3", "attrs": ["bold"] },
+ "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base01", "attrs": ["bold"] },
+ "paste_indicator": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] },
+ "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base01", "attrs": [] },
+ "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base01", "attrs": [] },
+ "branch:divider": { "fg": "solarized:base1", "bg": "solarized:base01", "attrs": [] },
+ "stash:divider": "branch:divider",
+ "file_name": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] },
+ "window_title": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "file_name_no_file": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": ["bold"] },
+ "file_format": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base01", "attrs": [] },
+ "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base01", "attrs": [] },
+ "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base01", "attrs": [] },
+ "line_percent": { "fg": "solarized:base3", "bg": "solarized:base00", "attrs": [] },
+ "line_percent_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base00", "attrs": [] },
+ "position": { "fg": "solarized:base3", "bg": "solarized:base00", "attrs": [] },
+ "position_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base00", "attrs": [] },
+ "line_current": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] },
+ "line_current_symbol": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "virtcol_current_gradient": { "fg": "GREEN_Orange_red", "bg": "solarized:base2", "attrs": [] },
+ "col_current": { "fg": "solarized:base0", "bg": "solarized:base2", "attrs": [] },
+ "environment": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "attached_clients": { "fg": "solarized:base1", "bg": "solarized:base02", "attrs": [] },
+ "error": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] },
+ "warning": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] },
+ "current_tag": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": ["bold"] }
+ },
+ "mode_translations": {
+ "nc": {
+ "colors": {
+ "solarized:base01": "solarized:base02",
+ "solarized:base00": "solarized:base02",
+ "solarized:base0": "solarized:base01",
+ "solarized:base1": "solarized:base00",
+ "solarized:base2": "solarized:base0",
+ "solarized:base3": "solarized:base1"
+ }
+ },
+ "i": {
+ "groups": {
+ "background": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "background:divider": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] },
+ "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": ["bold"] },
+ "paste_indicator": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] },
+ "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] },
+ "branch": { "fg": "solarized:base01", "bg": "solarized:base2", "attrs": [] },
+ "branch:divider": { "fg": "solarized:base00", "bg": "solarized:base2", "attrs": [] },
+ "file_directory": { "fg": "solarized:base01", "bg": "solarized:base2", "attrs": [] },
+ "file_name": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": ["bold"] },
+ "file_size": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] },
+ "file_name_no_file": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": ["bold"] },
+ "file_name_empty": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] },
+ "file_format": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] },
+ "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] },
+ "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": [] },
+ "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base2", "attrs": [] },
+ "line_percent": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] },
+ "line_percent_gradient": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] },
+ "position": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] },
+ "position_gradient": { "fg": "solarized:base3", "bg": "solarized:base1", "attrs": [] },
+ "line_current": { "fg": "solarized:base03", "bg": "solarized:base3", "attrs": ["bold"] },
+ "line_current_symbol": { "fg": "solarized:base03", "bg": "solarized:base3", "attrs": [] },
+ "col_current": { "fg": "solarized:base0", "bg": "solarized:base3", "attrs": [] }
+ }
+ },
+ "ic": {
+ "groups": {
+ "background": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "background:divider": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }
+ }
+ },
+ "ix": {
+ "groups": {
+ "background": { "fg": "solarized:base3", "bg": "solarized:base01", "attrs": [] },
+ "background:divider": { "fg": "solarized:base2", "bg": "solarized:base01", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }
+ }
+ },
+ "v": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }
+ }
+ },
+ "V": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }
+ }
+ },
+ "^V": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }
+ }
+ },
+ "R": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] }
+ }
+ },
+ "Rc": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] }
+ }
+ },
+ "Rx": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/colorschemes/vim/solarizedlight.json b/powerline/config_files/colorschemes/vim/solarizedlight.json
new file mode 100644
index 0000000..f862d39
--- /dev/null
+++ b/powerline/config_files/colorschemes/vim/solarizedlight.json
@@ -0,0 +1,122 @@
+{
+ "name": "Solarized light for vim",
+ "groups": {
+ "information:additional": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] },
+ "information:unimportant": { "fg": "solarized:base1", "bg": "solarized:base01", "attrs": [] },
+ "background": { "fg": "solarized:base03", "bg": "solarized:base01", "attrs": [] },
+ "background:divider": { "fg": "solarized:base0", "bg": "solarized:base01", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:green", "attrs": ["bold"] },
+ "visual_range": { "fg": "solarized:green", "bg": "solarized:base3", "attrs": ["bold"] },
+ "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": ["bold"] },
+ "paste_indicator": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": ["bold"] },
+ "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] },
+ "branch_dirty": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": [] },
+ "branch:divider": { "fg": "solarized:base1", "bg": "solarized:base2", "attrs": [] },
+ "stash": "branch_dirty",
+ "stash:divider": "branch:divider",
+ "file_name": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] },
+ "window_title": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "file_size": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "file_name_no_file": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] },
+ "file_name_empty": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base2", "attrs": [] },
+ "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base2", "attrs": [] },
+ "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base2", "attrs": [] },
+ "line_percent": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "line_percent_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base2", "attrs": [] },
+ "position": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "position_gradient": { "fg": "green_yellow_orange_red", "bg": "solarized:base2", "attrs": [] },
+ "line_current": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": ["bold"] },
+ "line_current_symbol": { "fg": "solarized:base3", "bg": "solarized:base02", "attrs": [] },
+ "virtcol_current_gradient": { "fg": "yellow_orange_red", "bg": "solarized:base02", "attrs": [] },
+ "col_current": { "fg": "solarized:base00", "bg": "solarized:base02", "attrs": [] },
+ "error": { "fg": "solarized:base03", "bg": "solarized:red", "attrs": ["bold"] },
+ "warning": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": ["bold"] },
+ "current_tag": { "fg": "solarized:base03", "bg": "solarized:base01", "attrs": ["bold"] }
+ },
+ "mode_translations": {
+ "nc": {
+ "colors": {
+ "solarized:base2": "solarized:base01",
+ "solarized:base0": "solarized:base01",
+ "solarized:base00": "solarized:base2",
+ "solarized:base1": "solarized:base0",
+ "solarized:base02": "solarized:base00",
+ "solarized:base03": "solarized:base1"
+ }
+ },
+ "i": {
+ "groups": {
+ "background": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "background:divider": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] },
+ "modified_indicator": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": ["bold"] },
+ "paste_indicator": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] },
+ "readonly_indicator": { "fg": "solarized:red", "bg": "solarized:base02", "attrs": [] },
+ "branch": { "fg": "solarized:base2", "bg": "solarized:base02", "attrs": [] },
+ "branch:divider": { "fg": "solarized:base0", "bg": "solarized:base02", "attrs": [] },
+ "file_directory": { "fg": "solarized:base2", "bg": "solarized:base02", "attrs": [] },
+ "file_name": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": ["bold"] },
+ "file_size": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": [] },
+ "file_name_no_file": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": ["bold"] },
+ "file_name_empty": { "fg": "solarized:base01", "bg": "solarized:base02", "attrs": [] },
+ "file_format": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] },
+ "file_vcs_status": { "fg": "solarized:red", "bg": "solarized:base02", "attrs": [] },
+ "file_vcs_status_M": { "fg": "solarized:yellow", "bg": "solarized:base02", "attrs": [] },
+ "file_vcs_status_A": { "fg": "solarized:green", "bg": "solarized:base02", "attrs": [] },
+ "line_percent": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] },
+ "line_percent_gradient": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] },
+ "position": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] },
+ "position_gradient": { "fg": "solarized:base03", "bg": "solarized:base1", "attrs": [] },
+ "line_current": { "fg": "solarized:base3", "bg": "solarized:base03", "attrs": ["bold"] },
+ "line_current_symbol": { "fg": "solarized:base3", "bg": "solarized:base03", "attrs": [] },
+ "virtcol_current_gradient": { "fg": "yellow_orange_red", "bg": "solarized:base03", "attrs": [] },
+ "col_current": { "fg": "solarized:base00", "bg": "solarized:base03", "attrs": [] }
+ }
+ },
+ "ic": {
+ "groups": {
+ "background": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "background:divider": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }
+ }
+ },
+ "ix": {
+ "groups": {
+ "background": { "fg": "solarized:base03", "bg": "solarized:base2", "attrs": [] },
+ "background:divider": { "fg": "solarized:base02", "bg": "solarized:base2", "attrs": [] },
+ "mode": { "fg": "solarized:base3", "bg": "solarized:blue", "attrs": ["bold"] }
+ }
+ },
+ "v": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }
+ }
+ },
+ "V": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }
+ }
+ },
+ "^V": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:orange", "attrs": ["bold"] }
+ }
+ },
+ "R": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] }
+ }
+ },
+ "Rc": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] }
+ }
+ },
+ "Rx": {
+ "groups": {
+ "mode": { "fg": "solarized:base3", "bg": "solarized:red", "attrs": ["bold"] }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/config.json b/powerline/config_files/config.json
new file mode 100644
index 0000000..4491885
--- /dev/null
+++ b/powerline/config_files/config.json
@@ -0,0 +1,53 @@
+{
+ "common": {
+ "term_truecolor": false
+ },
+ "ext": {
+ "ipython": {
+ "colorscheme": "default",
+ "theme": "in",
+ "local_themes": {
+ "rewrite": "rewrite",
+ "out": "out",
+ "in2": "in2"
+ }
+ },
+ "pdb": {
+ "colorscheme": "default",
+ "theme": "default"
+ },
+ "shell": {
+ "colorscheme": "default",
+ "theme": "default",
+ "local_themes": {
+ "continuation": "continuation",
+ "select": "select"
+ }
+ },
+ "tmux": {
+ "colorscheme": "default",
+ "theme": "default"
+ },
+ "vim": {
+ "colorscheme": "default",
+ "theme": "default",
+ "local_themes": {
+ "__tabline__": "tabline",
+
+ "cmdwin": "cmdwin",
+ "help": "help",
+ "quickfix": "quickfix",
+
+ "powerline.matchers.vim.plugin.nerdtree.nerdtree": "plugin_nerdtree",
+ "powerline.matchers.vim.plugin.commandt.commandt": "plugin_commandt",
+ "powerline.matchers.vim.plugin.gundo.gundo": "plugin_gundo",
+ "powerline.matchers.vim.plugin.gundo.gundo_preview": "plugin_gundo-preview"
+ }
+ },
+ "wm": {
+ "colorscheme": "default",
+ "theme": "default",
+ "update_interval": 2
+ }
+ }
+}
diff --git a/powerline/config_files/themes/ascii.json b/powerline/config_files/themes/ascii.json
new file mode 100644
index 0000000..0ea05e7
--- /dev/null
+++ b/powerline/config_files/themes/ascii.json
@@ -0,0 +1,153 @@
+{
+ "use_non_breaking_spaces": false,
+ "dividers": {
+ "left": {
+ "hard": " ",
+ "soft": "| "
+ },
+ "right": {
+ "hard": " ",
+ "soft": " |"
+ }
+ },
+ "spaces": 1,
+ "segment_data": {
+ "branch": {
+ "before": "BR "
+ },
+ "stash": {
+ "before": "ST "
+ },
+ "cwd": {
+ "args": {
+ "ellipsis": "..."
+ }
+ },
+ "player": {
+ "args": {
+ "state_symbols": {
+ "fallback": "",
+ "play": ">",
+ "pause": "~",
+ "stop": "X"
+ }
+ }
+ },
+
+ "line_current_symbol": {
+ "contents": "LN "
+ },
+
+ "time": {
+ "before": ""
+ },
+
+ "powerline.segments.common.net.network_load": {
+ "args": {
+ "recv_format": "DL {value:>8}",
+ "sent_format": "UL {value:>8}"
+ }
+ },
+ "powerline.segments.common.net.hostname": {
+ "before": "H "
+ },
+ "powerline.segments.common.bat.battery": {
+ "args": {
+ "full_heart": "O",
+ "empty_heart": "O",
+ "online": "C",
+ "offline": " "
+ }
+ },
+ "powerline.segments.common.sys.uptime": {
+ "before": "UP "
+ },
+ "powerline.segments.common.mail.email_imap_alert": {
+ "before": "MAIL "
+ },
+ "powerline.segments.common.env.virtualenv": {
+ "before": "(e) "
+ },
+ "powerline.segments.common.wthr.weather": {
+ "args": {
+ "icons": {
+ "day": "DAY",
+ "blustery": "WIND",
+ "rainy": "RAIN",
+ "cloudy": "CLOUDS",
+ "snowy": "SNOW",
+ "stormy": "STORM",
+ "foggy": "FOG",
+ "sunny": "SUN",
+ "night": "NIGHT",
+ "windy": "WINDY",
+ "not_available": "NA",
+ "unknown": "UKN"
+ },
+ "temp_format": "{temp:.0f} C"
+ }
+ },
+ "powerline.segments.common.time.fuzzy_time": {
+ "args": {
+ "unicode_text": false
+ }
+ },
+
+ "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",
+ "ic": "I-COMP",
+ "ix": "I-C_X ",
+ "R": "RPLACE",
+ "Rv": "V-RPLC",
+ "Rc": "R-COMP",
+ "Rx": "R-C_X ",
+ "c": "COMMND",
+ "cv": "VIM-EX",
+ "ce": "NRM-EX",
+ "r": "PROMPT",
+ "rm": "-MORE-",
+ "r?": "CNFIRM",
+ "!": "!SHELL",
+ "t": "TERM "
+ }
+ }
+ },
+ "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": "+"
+ }
+ },
+
+ "powerline.segments.i3wm.scratchpad": {
+ "args": {
+ "icons": {
+ "fresh": "O",
+ "changed": "X"
+ }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/themes/ipython/in.json b/powerline/config_files/themes/ipython/in.json
new file mode 100644
index 0000000..edd4d29
--- /dev/null
+++ b/powerline/config_files/themes/ipython/in.json
@@ -0,0 +1,25 @@
+{
+ "segments": {
+ "left": [
+ {
+ "function": "powerline.segments.common.env.virtualenv",
+ "priority": 10
+ },
+ {
+ "type": "string",
+ "contents": "In [",
+ "draw_soft_divider": false,
+ "highlight_groups": ["prompt"]
+ },
+ {
+ "function": "powerline.segments.ipython.prompt_count",
+ "draw_soft_divider": false
+ },
+ {
+ "type": "string",
+ "contents": "]",
+ "highlight_groups": ["prompt"]
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/ipython/in2.json b/powerline/config_files/themes/ipython/in2.json
new file mode 100644
index 0000000..422c44b
--- /dev/null
+++ b/powerline/config_files/themes/ipython/in2.json
@@ -0,0 +1,12 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "contents": "",
+ "width": "auto",
+ "highlight_groups": ["prompt"]
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/ipython/out.json b/powerline/config_files/themes/ipython/out.json
new file mode 100644
index 0000000..2425d0b
--- /dev/null
+++ b/powerline/config_files/themes/ipython/out.json
@@ -0,0 +1,24 @@
+{
+ "default_module": "powerline.segments.ipython",
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "contents": "Out[",
+ "draw_soft_divider": false,
+ "width": "auto",
+ "align": "r",
+ "highlight_groups": ["prompt"]
+ },
+ {
+ "function": "prompt_count",
+ "draw_soft_divider": false
+ },
+ {
+ "type": "string",
+ "contents": "]",
+ "highlight_groups": ["prompt"]
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/ipython/rewrite.json b/powerline/config_files/themes/ipython/rewrite.json
new file mode 100644
index 0000000..8192fe4
--- /dev/null
+++ b/powerline/config_files/themes/ipython/rewrite.json
@@ -0,0 +1,23 @@
+{
+ "default_module": "powerline.segments.ipython",
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "contents": "",
+ "draw_soft_divider": false,
+ "width": "auto",
+ "highlight_groups": ["prompt"]
+ },
+ {
+ "function": "prompt_count",
+ "draw_soft_divider": false
+ },
+ {
+ "type": "string",
+ "contents": ">",
+ "highlight_groups": ["prompt"]
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/pdb/default.json b/powerline/config_files/themes/pdb/default.json
new file mode 100644
index 0000000..dcae108
--- /dev/null
+++ b/powerline/config_files/themes/pdb/default.json
@@ -0,0 +1,27 @@
+{
+ "default_module": "powerline.segments.pdb",
+ "segments": {
+ "left": [
+ {
+ "function": "stack_depth"
+ },
+ {
+ "type": "segment_list",
+ "function": "powerline.listers.pdb.frame_lister",
+ "segments": [
+ {
+ "function": "current_file",
+ "after": ":"
+ },
+ {
+ "function": "current_line",
+ "after": " "
+ },
+ {
+ "function": "current_code_name"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/powerline.json b/powerline/config_files/themes/powerline.json
new file mode 100644
index 0000000..366a7ea
--- /dev/null
+++ b/powerline/config_files/themes/powerline.json
@@ -0,0 +1,151 @@
+{
+ "dividers": {
+ "left": {
+ "hard": " ",
+ "soft": " "
+ },
+ "right": {
+ "hard": " ",
+ "soft": " "
+ }
+ },
+ "spaces": 1,
+ "segment_data": {
+ "branch": {
+ "before": " "
+ },
+ "stash": {
+ "before": "⌆ "
+ },
+ "cwd": {
+ "args": {
+ "ellipsis": "⋯"
+ }
+ },
+
+ "line_current_symbol": {
+ "contents": " "
+ },
+ "player": {
+ "args": {
+ "state_symbols": {
+ "fallback": "♫",
+ "play": "▶",
+ "pause": "▮▮",
+ "stop": "■"
+ }
+ }
+ },
+
+ "time": {
+ "before": "⌚ "
+ },
+
+ "powerline.segments.common.net.network_load": {
+ "args": {
+ "recv_format": "⬇ {value:>8}",
+ "sent_format": "⬆ {value:>8}"
+ }
+ },
+ "powerline.segments.common.net.hostname": {
+ "before": " "
+ },
+ "powerline.segments.common.bat.battery": {
+ "args": {
+ "full_heart": "♥",
+ "empty_heart": "♥",
+ "online": "⚡︎",
+ "offline": " "
+ }
+ },
+ "powerline.segments.common.sys.uptime": {
+ "before": "⇑ "
+ },
+ "powerline.segments.common.mail.email_imap_alert": {
+ "before": "✉ "
+ },
+ "powerline.segments.common.env.virtualenv": {
+ "before": "ⓔ "
+ },
+ "powerline.segments.common.wthr.weather": {
+ "args": {
+ "icons": {
+ "day": "〇",
+ "blustery": "⚑",
+ "rainy": "☔",
+ "cloudy": "☁",
+ "snowy": "❅",
+ "stormy": "☈",
+ "foggy": "≡",
+ "sunny": "☼",
+ "night": "☾",
+ "windy": "☴",
+ "not_available": "�",
+ "unknown": "⚠"
+ }
+ }
+ },
+ "powerline.segments.common.time.fuzzy_time": {
+ "args": {
+ "unicode_text": true
+ }
+ },
+
+ "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",
+ "ic": "I·COMP",
+ "ix": "I·C-X ",
+ "R": "RPLACE",
+ "Rv": "V·RPLC",
+ "Rc": "R·COMP",
+ "Rx": "R·C-X ",
+ "c": "COMMND",
+ "cv": "VIM·EX",
+ "ce": "NRM·EX",
+ "r": "PROMPT",
+ "rm": "-MORE-",
+ "r?": "CNFIRM",
+ "!": "!SHELL",
+ "t": "TERM "
+ }
+ }
+ },
+ "powerline.segments.vim.visual_range": {
+ "args": {
+ "CTRL_V_text": "↕{rows} ↔{vcols}",
+ "v_text_oneline": "↔{vcols}",
+ "v_text_multiline": "↕{rows}",
+ "V_text": "⇕{rows}"
+ }
+ },
+ "powerline.segments.vim.readonly_indicator": {
+ "args": {
+ "text": ""
+ }
+ },
+ "powerline.segments.vim.modified_indicator": {
+ "args": {
+ "text": "+"
+ }
+ },
+
+ "powerline.segments.i3wm.scratchpad": {
+ "args": {
+ "icons": {
+ "fresh": "●",
+ "changed": "○"
+ }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/themes/powerline_terminus.json b/powerline/config_files/themes/powerline_terminus.json
new file mode 100644
index 0000000..e5fb1c8
--- /dev/null
+++ b/powerline/config_files/themes/powerline_terminus.json
@@ -0,0 +1,151 @@
+{
+ "dividers": {
+ "left": {
+ "hard": " ",
+ "soft": " "
+ },
+ "right": {
+ "hard": " ",
+ "soft": " "
+ }
+ },
+ "spaces": 1,
+ "segment_data": {
+ "branch": {
+ "before": " "
+ },
+ "stash": {
+ "before": "ST "
+ },
+ "cwd": {
+ "args": {
+ "ellipsis": "…"
+ }
+ },
+
+ "line_current_symbol": {
+ "contents": " "
+ },
+ "player": {
+ "args": {
+ "state_symbols": {
+ "fallback": "♫",
+ "play": "▶",
+ "pause": "▮▮",
+ "stop": "■"
+ }
+ }
+ },
+
+ "time": {
+ "before": ""
+ },
+
+ "powerline.segments.common.net.network_load": {
+ "args": {
+ "recv_format": "⇓ {value:>8}",
+ "sent_format": "⇑ {value:>8}"
+ }
+ },
+ "powerline.segments.common.net.hostname": {
+ "before": " "
+ },
+ "powerline.segments.common.bat.battery": {
+ "args": {
+ "full_heart": "♥",
+ "empty_heart": "♥",
+ "online": "⚡︎",
+ "offline": " "
+ }
+ },
+ "powerline.segments.common.sys.uptime": {
+ "before": "↑ "
+ },
+ "powerline.segments.common.mail.email_imap_alert": {
+ "before": "MAIL "
+ },
+ "powerline.segments.common.env.virtualenv": {
+ "before": "(e) "
+ },
+ "powerline.segments.common.wthr.weather": {
+ "args": {
+ "icons": {
+ "day": "DAY",
+ "blustery": "WIND",
+ "rainy": "RAIN",
+ "cloudy": "CLOUDS",
+ "snowy": "SNOW",
+ "stormy": "STORM",
+ "foggy": "FOG",
+ "sunny": "SUN",
+ "night": "NIGHT",
+ "windy": "WINDY",
+ "not_available": "NA",
+ "unknown": "UKN"
+ }
+ }
+ },
+ "powerline.segments.common.time.fuzzy_time": {
+ "args": {
+ "unicode_text": true
+ }
+ },
+
+ "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",
+ "ic": "I·COMP",
+ "ix": "I·C-X ",
+ "R": "RPLACE",
+ "Rv": "V·RPLC",
+ "Rc": "R·COMP",
+ "Rx": "R·C-X ",
+ "c": "COMMND",
+ "cv": "VIM·EX",
+ "ce": "NRM·EX",
+ "r": "PROMPT",
+ "rm": "-MORE-",
+ "r?": "CNFIRM",
+ "!": "!SHELL",
+ "t": "TERM "
+ }
+ }
+ },
+ "powerline.segments.vim.visual_range": {
+ "args": {
+ "CTRL_V_text": "↕{rows} ↔{vcols}",
+ "v_text_oneline": "↔{vcols}",
+ "v_text_multiline": "↕{rows}",
+ "V_text": "⇕{rows}"
+ }
+ },
+ "powerline.segments.vim.readonly_indicator": {
+ "args": {
+ "text": ""
+ }
+ },
+ "powerline.segments.vim.modified_indicator": {
+ "args": {
+ "text": "+"
+ }
+ },
+
+ "powerline.segments.i3wm.scratchpad": {
+ "args": {
+ "icons": {
+ "fresh": "●",
+ "changed": "○"
+ }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/themes/powerline_unicode7.json b/powerline/config_files/themes/powerline_unicode7.json
new file mode 100644
index 0000000..bd62826
--- /dev/null
+++ b/powerline/config_files/themes/powerline_unicode7.json
@@ -0,0 +1,165 @@
+{
+ "dividers": {
+ "left": {
+ "hard": " ",
+ "soft": " "
+ },
+ "right": {
+ "hard": " ",
+ "soft": " "
+ }
+ },
+ "spaces": 1,
+ "segment_data": {
+ "branch": {
+ "before": "🔀 "
+ },
+ "stash": {
+ "before": "📝"
+ },
+ "cwd": {
+ "args": {
+ "ellipsis": "⋯"
+ }
+ },
+
+ "line_current_symbol": {
+ "contents": " "
+ },
+ "player": {
+ "args": {
+ "state_symbols": {
+ "fallback": "♫",
+ "play": "⏵",
+ "pause": "⏸",
+ "stop": "⏹"
+ }
+ }
+ },
+
+ "time": {
+ "before": "🕐 "
+ },
+
+ "powerline.segments.common.net.network_load": {
+ "args": {
+ "recv_format": "⬇ {value:>8}",
+ "sent_format": "⬆ {value:>8}"
+ }
+ },
+ "powerline.segments.common.net.hostname": {
+ "before": "🏠 "
+ },
+ "powerline.segments.common.bat.battery": {
+ "args": {
+ "full_heart": "💙",
+ "empty_heart": "💛",
+ "online": "⚡️",
+ "offline": " "
+ }
+ },
+ "powerline.segments.common.sys.uptime": {
+ "before": "⇑ "
+ },
+ "powerline.segments.common.mail.email_imap_alert": {
+ "before": "✉ "
+ },
+ "powerline.segments.common.env.virtualenv": {
+ "before": "🐍 "
+ },
+ "powerline.segments.common.wthr.weather": {
+ "args": {
+ "icons": {
+ "tornado": "🌪",
+ "hurricane": "🌀",
+ "showers": "☔",
+ "scattered_showers": "☔",
+ "thunderstorms": "🌩",
+ "isolated_thunderstorms": "🌩",
+ "scattered_thunderstorms": "🌩",
+ "dust": "🌫",
+ "fog": "🌫",
+ "cold": "❄",
+ "partly_cloudy_day": "🌤",
+ "mostly_cloudy_day": "🌥",
+ "sun": "🌣",
+ "hot": "♨",
+ "day": "☀",
+ "blustery": "⚑",
+ "rainy": "☂",
+ "cloudy": "☁",
+ "snowy": "☃",
+ "stormy": "☈",
+ "foggy": "🌁",
+ "sunny": "🌣",
+ "night": "☾",
+ "windy": "☴",
+ "not_available": "�",
+ "unknown": "⚠"
+ }
+ }
+ },
+ "powerline.segments.common.time.fuzzy_time": {
+ "args": {
+ "unicode_text": true
+ }
+ },
+
+ "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",
+ "ic": "I·COMP",
+ "ix": "I·C-X ",
+ "R": "RPLACE",
+ "Rv": "V·RPLC",
+ "Rc": "R·COMP",
+ "Rx": "R·C-X ",
+ "c": "COMMND",
+ "cv": "VIM·EX",
+ "ce": "NRM·EX",
+ "r": "PROMPT",
+ "rm": "-MORE-",
+ "r?": "CNFIRM",
+ "!": "!SHELL",
+ "t": "TERM "
+ }
+ }
+ },
+ "powerline.segments.vim.visual_range": {
+ "args": {
+ "CTRL_V_text": "↕{rows} ↔{vcols}",
+ "v_text_oneline": "↔{vcols}",
+ "v_text_multiline": "↕{rows}",
+ "V_text": "⇕{rows}"
+ }
+ },
+ "powerline.segments.vim.readonly_indicator": {
+ "args": {
+ "text": "🔏"
+ }
+ },
+ "powerline.segments.vim.modified_indicator": {
+ "args": {
+ "text": "🖫⃥"
+ }
+ },
+
+ "powerline.segments.i3wm.scratchpad": {
+ "args": {
+ "icons": {
+ "fresh": "●",
+ "changed": "○"
+ }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/themes/shell/__main__.json b/powerline/config_files/themes/shell/__main__.json
new file mode 100644
index 0000000..13ae942
--- /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/continuation.json b/powerline/config_files/themes/shell/continuation.json
new file mode 100644
index 0000000..9307fc0
--- /dev/null
+++ b/powerline/config_files/themes/shell/continuation.json
@@ -0,0 +1,12 @@
+{
+ "default_module": "powerline.segments.shell",
+ "segments": {
+ "left": [
+ {
+ "function": "continuation"
+ }
+ ],
+ "right": [
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/shell/default.json b/powerline/config_files/themes/shell/default.json
new file mode 100644
index 0000000..38039d8
--- /dev/null
+++ b/powerline/config_files/themes/shell/default.json
@@ -0,0 +1,43 @@
+{
+ "segments": {
+ "left": [
+ {
+ "function": "powerline.segments.shell.mode"
+ },
+ {
+ "function": "powerline.segments.common.net.hostname",
+ "priority": 10
+ },
+ {
+ "function": "powerline.segments.common.env.user",
+ "priority": 30
+ },
+ {
+ "function": "powerline.segments.common.env.virtualenv",
+ "priority": 50
+ },
+ {
+ "function": "powerline.segments.shell.cwd",
+ "priority": 10
+ },
+ {
+ "function": "powerline.segments.shell.jobnum",
+ "priority": 20
+ }
+ ],
+ "right": [
+ {
+ "function": "powerline.segments.shell.last_pipe_status",
+ "priority": 10
+ },
+ {
+ "function": "powerline.segments.common.vcs.stash",
+ "priority": 50
+ },
+ {
+ "function": "powerline.segments.common.vcs.branch",
+ "priority": 40
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/shell/default_leftonly.json b/powerline/config_files/themes/shell/default_leftonly.json
new file mode 100644
index 0000000..b576273
--- /dev/null
+++ b/powerline/config_files/themes/shell/default_leftonly.json
@@ -0,0 +1,34 @@
+{
+ "segments": {
+ "left": [
+ {
+ "function": "powerline.segments.common.net.hostname",
+ "priority": 10
+ },
+ {
+ "function": "powerline.segments.common.env.user",
+ "priority": 30
+ },
+ {
+ "function": "powerline.segments.common.env.virtualenv",
+ "priority": 50
+ },
+ {
+ "function": "powerline.segments.common.vcs.branch",
+ "priority": 40
+ },
+ {
+ "function": "powerline.segments.shell.cwd",
+ "priority": 10
+ },
+ {
+ "function": "powerline.segments.shell.jobnum",
+ "priority": 20
+ },
+ {
+ "function": "powerline.segments.shell.last_pipe_status",
+ "priority": 10
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/shell/select.json b/powerline/config_files/themes/shell/select.json
new file mode 100644
index 0000000..3d81408
--- /dev/null
+++ b/powerline/config_files/themes/shell/select.json
@@ -0,0 +1,13 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "contents": "Select variant",
+ "width": "auto",
+ "align": "r",
+ "highlight_groups": ["continuation:current"]
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/tmux/default.json b/powerline/config_files/themes/tmux/default.json
new file mode 100644
index 0000000..4532ced
--- /dev/null
+++ b/powerline/config_files/themes/tmux/default.json
@@ -0,0 +1,28 @@
+{
+ "segments": {
+ "right": [
+ {
+ "function": "powerline.segments.common.sys.uptime",
+ "priority": 50
+ },
+ {
+ "function": "powerline.segments.common.sys.system_load",
+ "priority": 50
+ },
+ {
+ "function": "powerline.segments.common.time.date"
+ },
+ {
+ "function": "powerline.segments.common.time.date",
+ "name": "time",
+ "args": {
+ "format": "%H:%M",
+ "istime": true
+ }
+ },
+ {
+ "function": "powerline.segments.common.net.hostname"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/unicode.json b/powerline/config_files/themes/unicode.json
new file mode 100644
index 0000000..0802852
--- /dev/null
+++ b/powerline/config_files/themes/unicode.json
@@ -0,0 +1,151 @@
+{
+ "dividers": {
+ "left": {
+ "hard": "▌ ",
+ "soft": "│ "
+ },
+ "right": {
+ "hard": " ▐",
+ "soft": " │"
+ }
+ },
+ "spaces": 1,
+ "segment_data": {
+ "branch": {
+ "before": "⎇ "
+ },
+ "stash": {
+ "before": "⌆"
+ },
+ "cwd": {
+ "args": {
+ "ellipsis": "⋯"
+ }
+ },
+ "player": {
+ "args": {
+ "state_symbols": {
+ "fallback": "♫",
+ "play": "▶",
+ "pause": "▮▮",
+ "stop": "■"
+ }
+ }
+ },
+
+ "line_current_symbol": {
+ "contents": "␤ "
+ },
+
+ "time": {
+ "before": "⌚ "
+ },
+
+ "powerline.segments.common.net.network_load": {
+ "args": {
+ "recv_format": "⬇ {value:>8}",
+ "sent_format": "⬆ {value:>8}"
+ }
+ },
+ "powerline.segments.common.net.hostname": {
+ "before": "⌂ "
+ },
+ "powerline.segments.common.bat.battery": {
+ "args": {
+ "full_heart": "♥",
+ "empty_heart": "♥",
+ "online": "⚡︎",
+ "offline": " "
+ }
+ },
+ "powerline.segments.common.sys.uptime": {
+ "before": "⇑ "
+ },
+ "powerline.segments.common.mail.email_imap_alert": {
+ "before": "✉ "
+ },
+ "powerline.segments.common.env.virtualenv": {
+ "before": "ⓔ "
+ },
+ "powerline.segments.common.wthr.weather": {
+ "args": {
+ "icons": {
+ "day": "〇",
+ "blustery": "⚑",
+ "rainy": "☔",
+ "cloudy": "☁",
+ "snowy": "❅",
+ "stormy": "☈",
+ "foggy": "≡",
+ "sunny": "☼",
+ "night": "☾",
+ "windy": "☴",
+ "not_available": "�",
+ "unknown": "⚠"
+ }
+ }
+ },
+ "powerline.segments.common.time.fuzzy_time": {
+ "args": {
+ "unicode_text": true
+ }
+ },
+
+ "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",
+ "ic": "I·COMP",
+ "ix": "I·C-X ",
+ "R": "RPLACE",
+ "Rv": "V·RPLC",
+ "Rc": "R·COMP",
+ "Rx": "R·C-X ",
+ "c": "COMMND",
+ "cv": "VIM·EX",
+ "ce": "NRM·EX",
+ "r": "PROMPT",
+ "rm": "-MORE-",
+ "r?": "CNFIRM",
+ "!": "!SHELL",
+ "t": "TERM "
+ }
+ }
+ },
+ "powerline.segments.vim.visual_range": {
+ "args": {
+ "CTRL_V_text": "↕{rows} ↔{vcols}",
+ "v_text_oneline": "↔{vcols}",
+ "v_text_multiline": "↕{rows}",
+ "V_text": "⇕{rows}"
+ }
+ },
+ "powerline.segments.vim.readonly_indicator": {
+ "args": {
+ "text": "⊗"
+ }
+ },
+ "powerline.segments.vim.modified_indicator": {
+ "args": {
+ "text": "+"
+ }
+ },
+
+ "powerline.segments.i3wm.scratchpad": {
+ "args": {
+ "icons": {
+ "fresh": "●",
+ "changed": "○"
+ }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/themes/unicode_terminus.json b/powerline/config_files/themes/unicode_terminus.json
new file mode 100644
index 0000000..9c76985
--- /dev/null
+++ b/powerline/config_files/themes/unicode_terminus.json
@@ -0,0 +1,151 @@
+{
+ "dividers": {
+ "left": {
+ "hard": "▌ ",
+ "soft": "│ "
+ },
+ "right": {
+ "hard": " ▐",
+ "soft": " │"
+ }
+ },
+ "spaces": 1,
+ "segment_data": {
+ "branch": {
+ "before": "BR "
+ },
+ "stash": {
+ "before": "ST "
+ },
+ "cwd": {
+ "args": {
+ "ellipsis": "…"
+ }
+ },
+
+ "line_current_symbol": {
+ "contents": "␤ "
+ },
+ "player": {
+ "args": {
+ "state_symbols": {
+ "fallback": "♫",
+ "play": "▶",
+ "pause": "▮▮",
+ "stop": "■"
+ }
+ }
+ },
+
+ "time": {
+ "before": ""
+ },
+
+ "powerline.segments.common.net.network_load": {
+ "args": {
+ "recv_format": "⇓ {value:>8}",
+ "sent_format": "⇑ {value:>8}"
+ }
+ },
+ "powerline.segments.common.net.hostname": {
+ "before": "⌂ "
+ },
+ "powerline.segments.common.bat.battery": {
+ "args": {
+ "full_heart": "♥",
+ "empty_heart": "♥",
+ "online": "⚡︎",
+ "offline": " "
+ }
+ },
+ "powerline.segments.common.sys.uptime": {
+ "before": "↑ "
+ },
+ "powerline.segments.common.mail.email_imap_alert": {
+ "before": "MAIL "
+ },
+ "powerline.segments.common.env.virtualenv": {
+ "before": "(e) "
+ },
+ "powerline.segments.common.wthr.weather": {
+ "args": {
+ "icons": {
+ "day": "DAY",
+ "blustery": "WIND",
+ "rainy": "RAIN",
+ "cloudy": "CLOUDS",
+ "snowy": "SNOW",
+ "stormy": "STORM",
+ "foggy": "FOG",
+ "sunny": "SUN",
+ "night": "NIGHT",
+ "windy": "WINDY",
+ "not_available": "NA",
+ "unknown": "UKN"
+ }
+ }
+ },
+ "powerline.segments.common.time.fuzzy_time": {
+ "args": {
+ "unicode_text": true
+ }
+ },
+
+ "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",
+ "ic": "I·COMP",
+ "ix": "I·C-X ",
+ "R": "RPLACE",
+ "Rv": "V·RPLC",
+ "Rc": "R·COMP",
+ "Rx": "R·C-X ",
+ "c": "COMMND",
+ "cv": "VIM·EX",
+ "ce": "NRM·EX",
+ "r": "PROMPT",
+ "rm": "-MORE-",
+ "r?": "CNFIRM",
+ "!": "!SHELL",
+ "t": "TERM "
+ }
+ }
+ },
+ "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": "+"
+ }
+ },
+
+ "powerline.segments.i3wm.scratchpad": {
+ "args": {
+ "icons": {
+ "fresh": "●",
+ "changed": "○"
+ }
+ }
+ }
+ }
+}
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 0000000..421f5c8
--- /dev/null
+++ b/powerline/config_files/themes/unicode_terminus_condensed.json
@@ -0,0 +1,151 @@
+{
+ "dividers": {
+ "left": {
+ "hard": "▌",
+ "soft": "│"
+ },
+ "right": {
+ "hard": "▐",
+ "soft": "│"
+ }
+ },
+ "spaces": 0,
+ "segment_data": {
+ "branch": {
+ "before": "B "
+ },
+ "stash": {
+ "before": "S "
+ },
+ "cwd": {
+ "args": {
+ "use_path_separator": true,
+ "ellipsis": "…"
+ }
+ },
+
+ "line_current_symbol": {
+ "contents": "␤"
+ },
+ "player": {
+ "args": {
+ "state_symbols": {
+ "fallback": "♫",
+ "play": "▶",
+ "pause": "▮▮",
+ "stop": "■"
+ }
+ }
+ },
+
+ "time": {
+ "before": ""
+ },
+
+ "powerline.segments.common.net.network_load": {
+ "args": {
+ "recv_format": "⇓{value:>8}",
+ "sent_format": "⇑{value:>8}"
+ }
+ },
+ "powerline.segments.common.net.hostname": {
+ "before": "⌂"
+ },
+ "powerline.segments.common.bat.battery": {
+ "args": {
+ "full_heart": "♥",
+ "empty_heart": "♥",
+ "online": "⚡︎",
+ "offline": " "
+ }
+ },
+ "powerline.segments.common.sys.uptime": {
+ "before": "↑"
+ },
+ "powerline.segments.common.mail.email_imap_alert": {
+ "before": "M "
+ },
+ "powerline.segments.common.env.virtualenv": {
+ "before": "E "
+ },
+ "powerline.segments.common.wthr.weather": {
+ "args": {
+ "icons": {
+ "day": "D",
+ "blustery": "W",
+ "rainy": "R",
+ "cloudy": "c",
+ "snowy": "*",
+ "stormy": "S",
+ "foggy": "f",
+ "sunny": "s",
+ "night": "N",
+ "windy": "w",
+ "not_available": "-",
+ "unknown": "!"
+ }
+ }
+ },
+ "powerline.segments.common.time.fuzzy_time": {
+ "args": {
+ "unicode_text": true
+ }
+ },
+
+ "powerline.segments.vim.mode": {
+ "args": {
+ "override": {
+ "n": "NML",
+ "no": "NOP",
+ "v": "VIS",
+ "V": "VLN",
+ "^V": "VBL",
+ "s": "SEL",
+ "S": "SLN",
+ "^S": "SBL",
+ "i": "INS",
+ "ic": "I-C",
+ "ix": "I^X",
+ "R": "REP",
+ "Rv": "VRP",
+ "Rc": "R-C",
+ "Rx": "R^X",
+ "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": "↔{vcols}",
+ "v_text_multiline": "↕{rows}",
+ "V_text": "⇕{rows}"
+ }
+ },
+ "powerline.segments.vim.readonly_indicator": {
+ "args": {
+ "text": "RO"
+ }
+ },
+ "powerline.segments.vim.modified_indicator": {
+ "args": {
+ "text": "+"
+ }
+ },
+
+ "powerline.segments.i3wm.scratchpad": {
+ "args": {
+ "icons": {
+ "fresh": "●",
+ "changed": "○"
+ }
+ }
+ }
+ }
+}
diff --git a/powerline/config_files/themes/vim/__main__.json b/powerline/config_files/themes/vim/__main__.json
new file mode 100644
index 0000000..7cd3305
--- /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/cmdwin.json b/powerline/config_files/themes/vim/cmdwin.json
new file mode 100644
index 0000000..e6a05b0
--- /dev/null
+++ b/powerline/config_files/themes/vim/cmdwin.json
@@ -0,0 +1,18 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "contents": "Command Line",
+ "highlight_groups": ["file_name"]
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/default.json b/powerline/config_files/themes/vim/default.json
new file mode 100644
index 0000000..9b0c744
--- /dev/null
+++ b/powerline/config_files/themes/vim/default.json
@@ -0,0 +1,128 @@
+{
+ "segments": {
+ "left": [
+ {
+ "function": "mode",
+ "exclude_modes": ["nc"]
+ },
+ {
+ "function": "visual_range",
+ "include_modes": ["v", "V", "^V", "s", "S", "^S"],
+ "priority": 10
+ },
+ {
+ "function": "paste_indicator",
+ "exclude_modes": ["nc"],
+ "priority": 10
+ },
+ {
+ "function": "powerline.segments.vim.plugin.capslock.capslock_indicator",
+ "include_modes": ["i", "R", "Rv"],
+ "priority": 10
+ },
+ {
+ "function": "branch",
+ "exclude_modes": ["nc"],
+ "priority": 30
+ },
+ {
+ "function": "readonly_indicator",
+ "draw_soft_divider": false,
+ "after": " "
+ },
+ {
+ "function": "file_scheme",
+ "priority": 20
+ },
+ {
+ "function": "file_directory",
+ "priority": 40,
+ "draw_soft_divider": false
+ },
+ {
+ "function": "file_name",
+ "draw_soft_divider": false
+ },
+ {
+ "function": "file_vcs_status",
+ "before": " ",
+ "draw_soft_divider": false
+ },
+ {
+ "function": "modified_indicator",
+ "before": " "
+ },
+ {
+ "exclude_modes": ["i", "R", "Rv"],
+ "function": "trailing_whitespace",
+ "display": false,
+ "priority": 60
+ },
+ {
+ "exclude_modes": ["nc"],
+ "function": "powerline.segments.vim.plugin.syntastic.syntastic",
+ "priority": 50
+ },
+ {
+ "exclude_modes": ["nc"],
+ "function": "powerline.segments.vim.plugin.tagbar.current_tag",
+ "draw_soft_divider": false,
+ "priority": 50
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ {
+ "function": "file_format",
+ "draw_soft_divider": false,
+ "exclude_modes": ["nc"],
+ "priority": 60
+ },
+ {
+ "function": "file_encoding",
+ "exclude_modes": ["nc"],
+ "priority": 60
+ },
+ {
+ "function": "file_type",
+ "exclude_modes": ["nc"],
+ "priority": 60
+ },
+ {
+ "function": "line_percent",
+ "priority": 50,
+ "width": 4,
+ "align": "r"
+ },
+ {
+ "function": "csv_col_current",
+ "priority": 30
+ },
+ {
+ "type": "string",
+ "name": "line_current_symbol",
+ "highlight_groups": ["line_current_symbol", "line_current"]
+ },
+ {
+ "function": "line_current",
+ "draw_soft_divider": false,
+ "width": 3,
+ "align": "r"
+ },
+ {
+ "function": "virtcol_current",
+ "draw_soft_divider": false,
+ "priority": 20,
+ "before": ":",
+ "width": 3,
+ "align": "l"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/help.json b/powerline/config_files/themes/vim/help.json
new file mode 100644
index 0000000..45c9458
--- /dev/null
+++ b/powerline/config_files/themes/vim/help.json
@@ -0,0 +1,36 @@
+{
+ "segments": {
+ "left": [
+ {
+ "function": "file_name",
+ "draw_soft_divider": false
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ {
+ "function": "line_percent",
+ "priority": 30,
+ "width": 4,
+ "align": "r"
+ },
+ {
+ "type": "string",
+ "name": "line_current_symbol",
+ "highlight_groups": ["line_current_symbol", "line_current"]
+ },
+ {
+ "function": "line_current",
+ "draw_soft_divider": false,
+ "width": 3,
+ "align": "r"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/plugin_commandt.json b/powerline/config_files/themes/vim/plugin_commandt.json
new file mode 100644
index 0000000..dd6748f
--- /dev/null
+++ b/powerline/config_files/themes/vim/plugin_commandt.json
@@ -0,0 +1,26 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "contents": "Command-T",
+ "highlight_groups": ["commandt:label"]
+ },
+ {
+ "function": "powerline.segments.vim.plugin.commandt.finder"
+ },
+ {
+ "function": "powerline.segments.vim.plugin.commandt.path"
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["commandt:background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/plugin_gundo-preview.json b/powerline/config_files/themes/vim/plugin_gundo-preview.json
new file mode 100644
index 0000000..ad8432c
--- /dev/null
+++ b/powerline/config_files/themes/vim/plugin_gundo-preview.json
@@ -0,0 +1,18 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "highlight_groups": ["gundo:name", "file_name"],
+ "contents": "Undo diff"
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["gundo:background", "background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/plugin_gundo.json b/powerline/config_files/themes/vim/plugin_gundo.json
new file mode 100644
index 0000000..a03b256
--- /dev/null
+++ b/powerline/config_files/themes/vim/plugin_gundo.json
@@ -0,0 +1,18 @@
+{
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "highlight_groups": ["gundo:name", "file_name"],
+ "contents": "Undo tree"
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["gundo:background", "background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/plugin_nerdtree.json b/powerline/config_files/themes/vim/plugin_nerdtree.json
new file mode 100644
index 0000000..896d393
--- /dev/null
+++ b/powerline/config_files/themes/vim/plugin_nerdtree.json
@@ -0,0 +1,17 @@
+{
+ "default_module": "powerline.segments.vim.plugin.nerdtree",
+ "segments": {
+ "left": [
+ {
+ "function": "nerdtree"
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/quickfix.json b/powerline/config_files/themes/vim/quickfix.json
new file mode 100644
index 0000000..ae4d5a5
--- /dev/null
+++ b/powerline/config_files/themes/vim/quickfix.json
@@ -0,0 +1,40 @@
+{
+ "segment_data": {
+ "buffer_name": {
+ "contents": "Location List"
+ }
+ },
+ "segments": {
+ "left": [
+ {
+ "type": "string",
+ "name": "buffer_name",
+ "highlight_groups": ["file_name"]
+ },
+ {
+ "function": "window_title",
+ "draw_soft_divider": false
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ {
+ "type": "string",
+ "name": "line_current_symbol",
+ "highlight_groups": ["line_current_symbol", "line_current"]
+ },
+ {
+ "function": "line_current",
+ "draw_soft_divider": false,
+ "width": 3,
+ "align": "r"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/vim/tabline.json b/powerline/config_files/themes/vim/tabline.json
new file mode 100644
index 0000000..1e3130e
--- /dev/null
+++ b/powerline/config_files/themes/vim/tabline.json
@@ -0,0 +1,93 @@
+{
+ "default_module": "powerline.segments.vim",
+ "segments": {
+ "left": [
+ {
+ "type": "segment_list",
+ "function": "powerline.listers.vim.tablister",
+ "exclude_function": "single_tab",
+ "segments": [
+ {
+ "function": "tab"
+ },
+ {
+ "function": "tabnr",
+ "after": " ",
+ "priority": 5
+ },
+ {
+ "function": "file_directory",
+ "priority": 40
+ },
+ {
+ "function": "file_name",
+ "args": {
+ "display_no_file": true
+ },
+ "priority": 10
+ },
+ {
+ "function": "tab_modified_indicator",
+ "priority": 5
+ }
+ ]
+ },
+ {
+ "function": "tab",
+ "args": {
+ "end": true
+ }
+ },
+ {
+ "type": "segment_list",
+ "function": "powerline.listers.vim.bufferlister",
+ "include_function": "single_tab",
+ "segments": [
+ {
+ "function": "bufnr",
+ "after": " ",
+ "priority": 5
+ },
+ {
+ "function": "file_directory",
+ "priority": 40
+ },
+ {
+ "function": "file_name",
+ "args": {
+ "display_no_file": true
+ },
+ "priority": 10
+ },
+ {
+ "function": "modified_indicator",
+ "priority": 5
+ }
+ ]
+ },
+ {
+ "type": "string",
+ "highlight_groups": ["tab:background"],
+ "draw_soft_divider": false,
+ "draw_hard_divider": false,
+ "width": "auto"
+ }
+ ],
+ "right": [
+ {
+ "type": "string",
+ "contents": "Bufs",
+ "name": "single_tab",
+ "highlight_groups": ["single_tab"],
+ "include_function": "single_tab"
+ },
+ {
+ "type": "string",
+ "contents": "Tabs",
+ "name": "many_tabs",
+ "highlight_groups": ["many_tabs"],
+ "exclude_function": "single_tab"
+ }
+ ]
+ }
+}
diff --git a/powerline/config_files/themes/wm/default.json b/powerline/config_files/themes/wm/default.json
new file mode 100644
index 0000000..3d468b4
--- /dev/null
+++ b/powerline/config_files/themes/wm/default.json
@@ -0,0 +1,17 @@
+{
+ "segments": {
+ "right": [
+ {
+ "function": "powerline.segments.common.time.date"
+ },
+ {
+ "function": "powerline.segments.common.time.date",
+ "name": "time",
+ "args": {
+ "format": "%H:%M",
+ "istime": true
+ }
+ }
+ ]
+ }
+}
diff --git a/powerline/dist/systemd/powerline-daemon.service b/powerline/dist/systemd/powerline-daemon.service
new file mode 100644
index 0000000..96b685d
--- /dev/null
+++ b/powerline/dist/systemd/powerline-daemon.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=powerline-daemon - Daemon that improves powerline performance
+Documentation=man:powerline-daemon(1)
+Documentation=https://powerline.readthedocs.org/en/latest/
+
+[Service]
+ExecStart=/usr/bin/powerline-daemon --foreground
+
+[Install]
+WantedBy=default.target
diff --git a/powerline/ipython.py b/powerline/ipython.py
new file mode 100644
index 0000000..cb84fc7
--- /dev/null
+++ b/powerline/ipython.py
@@ -0,0 +1,68 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline import Powerline
+from powerline.lib.dict import mergedicts
+from powerline.lib.unicode import string
+
+
+class IPythonInfo(object):
+ def __init__(self, shell):
+ self._shell = shell
+
+ @property
+ def prompt_count(self):
+ return self._shell.execution_count
+
+
+# HACK: ipython tries to only leave us with plain ASCII
+class RewriteResult(object):
+ def __init__(self, prompt):
+ self.prompt = string(prompt)
+
+ def __str__(self):
+ return self.prompt
+
+ def __add__(self, s):
+ if type(s) is not str:
+ try:
+ s = s.encode('utf-8')
+ except AttributeError:
+ raise NotImplementedError
+ return RewriteResult(self.prompt + s)
+
+
+class IPythonPowerline(Powerline):
+ def init(self, **kwargs):
+ super(IPythonPowerline, self).init(
+ 'ipython',
+ use_daemon_threads=True,
+ **kwargs
+ )
+
+ def get_config_paths(self):
+ if self.config_paths:
+ return self.config_paths
+ else:
+ return super(IPythonPowerline, self).get_config_paths()
+
+ def get_local_themes(self, local_themes):
+ return dict(((type, {'config': self.load_theme_config(name)}) for type, name in local_themes.items()))
+
+ def load_main_config(self):
+ r = super(IPythonPowerline, self).load_main_config()
+ if self.config_overrides:
+ mergedicts(r, self.config_overrides)
+ return r
+
+ def load_theme_config(self, name):
+ r = super(IPythonPowerline, self).load_theme_config(name)
+ if name in self.theme_overrides:
+ mergedicts(r, self.theme_overrides[name])
+ return r
+
+ def do_setup(self, wrefs):
+ for wref in wrefs:
+ obj = wref()
+ if obj is not None:
+ setattr(obj, 'powerline', self)
diff --git a/powerline/lemonbar.py b/powerline/lemonbar.py
new file mode 100644
index 0000000..b49f86b
--- /dev/null
+++ b/powerline/lemonbar.py
@@ -0,0 +1,21 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline import Powerline
+from powerline.lib.dict import mergedicts
+
+
+class LemonbarPowerline(Powerline):
+ def init(self):
+ super(LemonbarPowerline, self).init(ext='wm', renderer_module='lemonbar')
+
+ get_encoding = staticmethod(lambda: 'utf-8')
+
+ def get_local_themes(self, local_themes):
+ if not local_themes:
+ return {}
+
+ return dict((
+ (key, {'config': self.load_theme_config(val)})
+ for key, val in local_themes.items()
+ ))
diff --git a/powerline/lib/__init__.py b/powerline/lib/__init__.py
new file mode 100644
index 0000000..2a5fbd0
--- /dev/null
+++ b/powerline/lib/__init__.py
@@ -0,0 +1,28 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from functools import wraps
+
+
+def wraps_saveargs(wrapped):
+ def dec(wrapper):
+ r = wraps(wrapped)(wrapper)
+ r.powerline_origin = getattr(wrapped, 'powerline_origin', wrapped)
+ return r
+ return dec
+
+
+def add_divider_highlight_group(highlight_group):
+ def dec(func):
+ @wraps_saveargs(func)
+ def f(**kwargs):
+ r = func(**kwargs)
+ if r:
+ return [{
+ 'contents': r,
+ 'divider_highlight_group': highlight_group,
+ }]
+ else:
+ return None
+ return f
+ return dec
diff --git a/powerline/lib/config.py b/powerline/lib/config.py
new file mode 100644
index 0000000..0c95e47
--- /dev/null
+++ b/powerline/lib/config.py
@@ -0,0 +1,218 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import json
+import codecs
+
+from copy import deepcopy
+from threading import Event, Lock
+from collections import defaultdict
+
+from powerline.lib.threaded import MultiRunnedThread
+from powerline.lib.watcher import create_file_watcher
+
+
+def open_file(path):
+ return codecs.open(path, encoding='utf-8')
+
+
+def load_json_config(config_file_path, load=json.load, open_file=open_file):
+ with open_file(config_file_path) as config_file_fp:
+ return load(config_file_fp)
+
+
+class DummyWatcher(object):
+ def __call__(self, *args, **kwargs):
+ return False
+
+ def watch(self, *args, **kwargs):
+ pass
+
+
+class DeferredWatcher(object):
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+ self.calls = []
+
+ def __call__(self, *args, **kwargs):
+ self.calls.append(('__call__', args, kwargs))
+
+ def watch(self, *args, **kwargs):
+ self.calls.append(('watch', args, kwargs))
+
+ def unwatch(self, *args, **kwargs):
+ self.calls.append(('unwatch', args, kwargs))
+
+ def transfer_calls(self, watcher):
+ for attr, args, kwargs in self.calls:
+ getattr(watcher, attr)(*args, **kwargs)
+
+
+class ConfigLoader(MultiRunnedThread):
+ def __init__(self, shutdown_event=None, watcher=None, watcher_type=None, load=load_json_config, run_once=False):
+ super(ConfigLoader, self).__init__()
+ self.shutdown_event = shutdown_event or Event()
+ if run_once:
+ self.watcher = DummyWatcher()
+ self.watcher_type = 'dummy'
+ else:
+ self.watcher = watcher or DeferredWatcher()
+ if watcher:
+ if not watcher_type:
+ raise ValueError('When specifying watcher you must also specify watcher type')
+ self.watcher_type = watcher_type
+ else:
+ self.watcher_type = 'deferred'
+ self._load = load
+
+ self.pl = None
+ self.interval = None
+
+ self.lock = Lock()
+
+ self.watched = defaultdict(set)
+ self.missing = defaultdict(set)
+ self.loaded = {}
+
+ def set_watcher(self, watcher_type, force=False):
+ if watcher_type == self.watcher_type:
+ return
+ watcher = create_file_watcher(self.pl, watcher_type)
+ with self.lock:
+ if self.watcher_type == 'deferred':
+ self.watcher.transfer_calls(watcher)
+ self.watcher = watcher
+ self.watcher_type = watcher_type
+
+ def set_pl(self, pl):
+ self.pl = pl
+
+ def set_interval(self, interval):
+ self.interval = interval
+
+ def register(self, function, path):
+ '''Register function that will be run when file changes.
+
+ :param function function:
+ Function that will be called when file at the given path changes.
+ :param str path:
+ Path that will be watched for.
+ '''
+ with self.lock:
+ self.watched[path].add(function)
+ self.watcher.watch(path)
+
+ def register_missing(self, condition_function, function, key):
+ '''Register any function that will be called with given key each
+ interval seconds (interval is defined at __init__). Its result is then
+ passed to ``function``, but only if the result is true.
+
+ :param function condition_function:
+ Function which will be called each ``interval`` seconds. All
+ exceptions from it will be logged and ignored. IOError exception
+ will be ignored without logging.
+ :param function function:
+ Function which will be called if condition_function returns
+ something that is true. Accepts result of condition_function as an
+ argument.
+ :param str key:
+ Any value, it will be passed to condition_function on each call.
+
+ Note: registered functions will be automatically removed if
+ condition_function results in something true.
+ '''
+ with self.lock:
+ self.missing[key].add((condition_function, function))
+
+ def unregister_functions(self, removed_functions):
+ '''Unregister files handled by these functions.
+
+ :param set removed_functions:
+ Set of functions previously passed to ``.register()`` method.
+ '''
+ with self.lock:
+ for path, functions in list(self.watched.items()):
+ functions -= removed_functions
+ if not functions:
+ self.watched.pop(path)
+ self.loaded.pop(path, None)
+
+ def unregister_missing(self, removed_functions):
+ '''Unregister files handled by these functions.
+
+ :param set removed_functions:
+ Set of pairs (2-tuples) representing ``(condition_function,
+ function)`` function pairs previously passed as an arguments to
+ ``.register_missing()`` method.
+ '''
+ with self.lock:
+ for key, functions in list(self.missing.items()):
+ functions -= removed_functions
+ if not functions:
+ self.missing.pop(key)
+
+ def load(self, path):
+ try:
+ # No locks: GIL does what we need
+ return deepcopy(self.loaded[path])
+ except KeyError:
+ r = self._load(path)
+ self.loaded[path] = deepcopy(r)
+ return r
+
+ def update(self):
+ toload = []
+ with self.lock:
+ for path, functions in self.watched.items():
+ for function in functions:
+ try:
+ modified = self.watcher(path)
+ except OSError as e:
+ modified = True
+ self.exception('Error while running watcher for path {0}: {1}', path, str(e))
+ else:
+ if modified:
+ toload.append(path)
+ if modified:
+ function(path)
+ with self.lock:
+ for key, functions in list(self.missing.items()):
+ for condition_function, function in list(functions):
+ try:
+ path = condition_function(key)
+ except IOError:
+ pass
+ except Exception as e:
+ self.exception('Error while running condition function for key {0}: {1}', key, str(e))
+ else:
+ if path:
+ toload.append(path)
+ function(path)
+ functions.remove((condition_function, function))
+ if not functions:
+ self.missing.pop(key)
+ for path in toload:
+ try:
+ self.loaded[path] = deepcopy(self._load(path))
+ except Exception as e:
+ self.exception('Error while loading {0}: {1}', path, str(e))
+ try:
+ self.loaded.pop(path)
+ except KeyError:
+ pass
+ try:
+ self.loaded.pop(path)
+ except KeyError:
+ pass
+
+ def run(self):
+ while self.interval is not None and not self.shutdown_event.is_set():
+ self.update()
+ self.shutdown_event.wait(self.interval)
+
+ def exception(self, msg, *args, **kwargs):
+ if self.pl:
+ self.pl.exception(msg, prefix='config_loader', *args, **kwargs)
+ else:
+ raise
diff --git a/powerline/lib/debug.py b/powerline/lib/debug.py
new file mode 100755
index 0000000..515e8c4
--- /dev/null
+++ b/powerline/lib/debug.py
@@ -0,0 +1,97 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import gc
+import sys
+
+from types import FrameType
+from itertools import chain
+
+
+# From http://code.activestate.com/recipes/523004-find-cyclical-references/
+def print_cycles(objects, outstream=sys.stdout, show_progress=False):
+ '''Find reference cycles
+
+ :param list objects:
+ A list of objects to find cycles in. It is often useful to pass in
+ gc.garbage to find the cycles that are preventing some objects from
+ being garbage collected.
+ :param file outstream:
+ The stream for output.
+ :param bool show_progress:
+ If True, print the number of objects reached as they are found.
+ '''
+ def print_path(path):
+ for i, step in enumerate(path):
+ # next “wraps around”
+ next = path[(i + 1) % len(path)]
+
+ outstream.write(' %s -- ' % str(type(step)))
+ written = False
+ if isinstance(step, dict):
+ for key, val in step.items():
+ if val is next:
+ outstream.write('[%s]' % repr(key))
+ written = True
+ break
+ if key is next:
+ outstream.write('[key] = %s' % repr(val))
+ written = True
+ break
+ elif isinstance(step, (list, tuple)):
+ for i, item in enumerate(step):
+ if item is next:
+ outstream.write('[%d]' % i)
+ written = True
+ elif getattr(type(step), '__getattribute__', None) in (object.__getattribute__, type.__getattribute__):
+ for attr in chain(dir(step), getattr(step, '__dict__', ())):
+ if getattr(step, attr, None) is next:
+ try:
+ outstream.write('%r.%s' % (step, attr))
+ except TypeError:
+ outstream.write('.%s' % (step, attr))
+ written = True
+ break
+ if not written:
+ outstream.write(repr(step))
+ outstream.write(' ->\n')
+ outstream.write('\n')
+
+ def recurse(obj, start, all, current_path):
+ if show_progress:
+ outstream.write('%d\r' % len(all))
+
+ all[id(obj)] = None
+
+ referents = gc.get_referents(obj)
+ for referent in referents:
+ # If we’ve found our way back to the start, this is
+ # a cycle, so print it out
+ if referent is start:
+ try:
+ outstream.write('Cyclic reference: %r\n' % referent)
+ except TypeError:
+ try:
+ outstream.write('Cyclic reference: %i (%r)\n' % (id(referent), type(referent)))
+ except TypeError:
+ outstream.write('Cyclic reference: %i\n' % id(referent))
+ print_path(current_path)
+
+ # Don’t go back through the original list of objects, or
+ # through temporary references to the object, since those
+ # are just an artifact of the cycle detector itself.
+ elif referent is objects or isinstance(referent, FrameType):
+ continue
+
+ # We haven’t seen this object before, so recurse
+ elif id(referent) not in all:
+ recurse(referent, start, all, current_path + (obj,))
+
+ for obj in objects:
+ # We are not interested in non-powerline cyclic references
+ try:
+ if not type(obj).__module__.startswith('powerline'):
+ continue
+ except AttributeError:
+ continue
+ recurse(obj, obj, {}, ())
diff --git a/powerline/lib/dict.py b/powerline/lib/dict.py
new file mode 100644
index 0000000..c06ab30
--- /dev/null
+++ b/powerline/lib/dict.py
@@ -0,0 +1,88 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+
+REMOVE_THIS_KEY = object()
+
+
+def mergeargs(argvalue, remove=False):
+ if not argvalue:
+ return None
+ r = {}
+ for subval in argvalue:
+ mergedicts(r, dict([subval]), remove=remove)
+ return r
+
+
+def _clear_special_values(d):
+ '''Remove REMOVE_THIS_KEY values from dictionary
+ '''
+ l = [d]
+ while l:
+ i = l.pop()
+ pops = []
+ for k, v in i.items():
+ if v is REMOVE_THIS_KEY:
+ pops.append(k)
+ elif isinstance(v, dict):
+ l.append(v)
+ for k in pops:
+ i.pop(k)
+
+
+def mergedicts(d1, d2, remove=True):
+ '''Recursively merge two dictionaries
+
+ First dictionary is modified in-place.
+ '''
+ _setmerged(d1, d2)
+ for k in d2:
+ if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict):
+ mergedicts(d1[k], d2[k], remove)
+ elif remove and d2[k] is REMOVE_THIS_KEY:
+ d1.pop(k, None)
+ else:
+ if remove and isinstance(d2[k], dict):
+ _clear_special_values(d2[k])
+ d1[k] = d2[k]
+
+
+def mergedefaults(d1, d2):
+ '''Recursively merge two dictionaries, keeping existing values
+
+ First dictionary is modified in-place.
+ '''
+ for k in d2:
+ if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict):
+ mergedefaults(d1[k], d2[k])
+ else:
+ d1.setdefault(k, d2[k])
+
+
+def _setmerged(d1, d2):
+ if hasattr(d1, 'setmerged'):
+ d1.setmerged(d2)
+
+
+def mergedicts_copy(d1, d2):
+ '''Recursively merge two dictionaries.
+
+ Dictionaries are not modified. Copying happens only if necessary. Assumes
+ that first dictionary supports .copy() method.
+ '''
+ ret = d1.copy()
+ _setmerged(ret, d2)
+ for k in d2:
+ if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict):
+ ret[k] = mergedicts_copy(d1[k], d2[k])
+ else:
+ ret[k] = d2[k]
+ return ret
+
+
+def updated(d, *args, **kwargs):
+ '''Copy dictionary and update it with provided arguments
+ '''
+ d = d.copy()
+ d.update(*args, **kwargs)
+ return d
diff --git a/powerline/lib/encoding.py b/powerline/lib/encoding.py
new file mode 100644
index 0000000..76a51d8
--- /dev/null
+++ b/powerline/lib/encoding.py
@@ -0,0 +1,125 @@
+# vim:fileencoding=utf-8:noet
+
+'''Encodings support
+
+This is the only module from which functions obtaining encoding should be
+exported. Note: you should always care about errors= argument since it is not
+guaranteed that encoding returned by some function can encode/decode given
+string.
+
+All functions in this module must always return a valid encoding. Most of them
+are not thread-safe.
+'''
+
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import locale
+
+
+def get_preferred_file_name_encoding():
+ '''Get preferred file name encoding
+ '''
+ return (
+ sys.getfilesystemencoding()
+ or locale.getpreferredencoding()
+ or 'utf-8'
+ )
+
+
+def get_preferred_file_contents_encoding():
+ '''Get encoding preferred for file contents
+ '''
+ return (
+ locale.getpreferredencoding()
+ or 'utf-8'
+ )
+
+
+def get_preferred_output_encoding():
+ '''Get encoding that should be used for printing strings
+
+ .. warning::
+ Falls back to ASCII, so that output is most likely to be displayed
+ correctly.
+ '''
+ if hasattr(locale, 'LC_MESSAGES'):
+ return (
+ locale.getlocale(locale.LC_MESSAGES)[1]
+ or locale.getdefaultlocale()[1]
+ or 'ascii'
+ )
+
+ return (
+ locale.getdefaultlocale()[1]
+ or 'ascii'
+ )
+
+
+def get_preferred_input_encoding():
+ '''Get encoding that should be used for reading shell command output
+
+ .. warning::
+ Falls back to latin1 so that function is less likely to throw as decoded
+ output is primary searched for ASCII values.
+ '''
+ if hasattr(locale, 'LC_MESSAGES'):
+ return (
+ locale.getlocale(locale.LC_MESSAGES)[1]
+ or locale.getdefaultlocale()[1]
+ or 'latin1'
+ )
+
+ return (
+ locale.getdefaultlocale()[1]
+ or 'latin1'
+ )
+
+
+def get_preferred_arguments_encoding():
+ '''Get encoding that should be used for command-line arguments
+
+ .. warning::
+ Falls back to latin1 so that function is less likely to throw as
+ non-ASCII command-line arguments most likely contain non-ASCII
+ filenames and screwing them up due to unidentified locale is not much of
+ a problem.
+ '''
+ return (
+ locale.getdefaultlocale()[1]
+ or 'latin1'
+ )
+
+
+def get_preferred_environment_encoding():
+ '''Get encoding that should be used for decoding environment variables
+ '''
+ return (
+ locale.getpreferredencoding()
+ or 'utf-8'
+ )
+
+
+def get_unicode_writer(stream=sys.stdout, encoding=None, errors='replace'):
+ '''Get function which will write unicode string to the given stream
+
+ Writing is done using encoding returned by
+ :py:func:`get_preferred_output_encoding`.
+
+ :param file stream:
+ Stream to write to. Default value is :py:attr:`sys.stdout`.
+ :param str encoding:
+ Determines which encoding to use. If this argument is specified then
+ :py:func:`get_preferred_output_encoding` is not used.
+ :param str errors:
+ Determines what to do with characters which cannot be encoded. See
+ ``errors`` argument of :py:func:`codecs.encode`.
+
+ :return: Callable which writes unicode string to the given stream using
+ the preferred output encoding.
+ '''
+ encoding = encoding or get_preferred_output_encoding()
+ if sys.version_info < (3,) or not hasattr(stream, 'buffer'):
+ return lambda s: stream.write(s.encode(encoding, errors))
+ else:
+ return lambda s: stream.buffer.write(s.encode(encoding, errors))
diff --git a/powerline/lib/humanize_bytes.py b/powerline/lib/humanize_bytes.py
new file mode 100644
index 0000000..c98a117
--- /dev/null
+++ b/powerline/lib/humanize_bytes.py
@@ -0,0 +1,25 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from math import log
+
+
+unit_list = tuple(zip(['', 'k', 'M', 'G', 'T', 'P'], [0, 0, 1, 2, 2, 2]))
+
+
+def humanize_bytes(num, suffix='B', si_prefix=False):
+ '''Return a human friendly byte representation.
+
+ Modified version from http://stackoverflow.com/questions/1094841
+ '''
+ if num == 0:
+ return '0 ' + suffix
+ div = 1000 if si_prefix else 1024
+ exponent = min(int(log(num, div)) if num else 0, len(unit_list) - 1)
+ quotient = float(num) / div ** exponent
+ unit, decimals = unit_list[exponent]
+ if unit and not si_prefix:
+ unit = unit.upper() + 'i'
+ return ('{{quotient:.{decimals}f}} {{unit}}{{suffix}}'
+ .format(decimals=decimals)
+ .format(quotient=quotient, unit=unit, suffix=suffix))
diff --git a/powerline/lib/inotify.py b/powerline/lib/inotify.py
new file mode 100644
index 0000000..8b74a7f
--- /dev/null
+++ b/powerline/lib/inotify.py
@@ -0,0 +1,184 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import os
+import errno
+import ctypes
+import struct
+
+from ctypes.util import find_library
+
+from powerline.lib.encoding import get_preferred_file_name_encoding
+
+
+__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
+__docformat__ = 'restructuredtext en'
+
+
+class INotifyError(Exception):
+ pass
+
+
+_inotify = None
+
+
+def load_inotify():
+ ''' Initialize the inotify library '''
+ global _inotify
+ if _inotify is None:
+ if hasattr(sys, 'getwindowsversion'):
+ # On windows abort before loading the C library. Windows has
+ # multiple, incompatible C runtimes, and we have no way of knowing
+ # if the one chosen by ctypes is compatible with the currently
+ # loaded one.
+ raise INotifyError('INotify not available on windows')
+ if sys.platform == 'darwin':
+ raise INotifyError('INotify not available on OS X')
+ if not hasattr(ctypes, 'c_ssize_t'):
+ raise INotifyError('You need python >= 2.7 to use inotify')
+ name = find_library('c')
+ if not name:
+ raise INotifyError('Cannot find C library')
+ libc = ctypes.CDLL(name, use_errno=True)
+ for function in ('inotify_add_watch', 'inotify_init1', 'inotify_rm_watch'):
+ if not hasattr(libc, function):
+ raise INotifyError('libc is too old')
+ # inotify_init1()
+ prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, use_errno=True)
+ init1 = prototype(('inotify_init1', libc), ((1, 'flags', 0),))
+
+ # inotify_add_watch()
+ prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32, use_errno=True)
+ add_watch = prototype(('inotify_add_watch', libc), (
+ (1, 'fd'), (1, 'pathname'), (1, 'mask')))
+
+ # inotify_rm_watch()
+ prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, use_errno=True)
+ rm_watch = prototype(('inotify_rm_watch', libc), (
+ (1, 'fd'), (1, 'wd')))
+
+ # read()
+ prototype = ctypes.CFUNCTYPE(ctypes.c_ssize_t, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t, use_errno=True)
+ read = prototype(('read', libc), (
+ (1, 'fd'), (1, 'buf'), (1, 'count')))
+ _inotify = (init1, add_watch, rm_watch, read)
+ return _inotify
+
+
+class INotify(object):
+
+ # See <sys/inotify.h> for the flags defined below
+
+ # Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.
+ ACCESS = 0x00000001 # File was accessed.
+ MODIFY = 0x00000002 # File was modified.
+ ATTRIB = 0x00000004 # Metadata changed.
+ CLOSE_WRITE = 0x00000008 # Writtable file was closed.
+ CLOSE_NOWRITE = 0x00000010 # Unwrittable file closed.
+ OPEN = 0x00000020 # File was opened.
+ MOVED_FROM = 0x00000040 # File was moved from X.
+ MOVED_TO = 0x00000080 # File was moved to Y.
+ CREATE = 0x00000100 # Subfile was created.
+ DELETE = 0x00000200 # Subfile was deleted.
+ DELETE_SELF = 0x00000400 # Self was deleted.
+ MOVE_SELF = 0x00000800 # Self was moved.
+
+ # Events sent by the kernel.
+ UNMOUNT = 0x00002000 # Backing fs was unmounted.
+ Q_OVERFLOW = 0x00004000 # Event queued overflowed.
+ IGNORED = 0x00008000 # File was ignored.
+
+ # Helper events.
+ CLOSE = (CLOSE_WRITE | CLOSE_NOWRITE) # Close.
+ MOVE = (MOVED_FROM | MOVED_TO) # Moves.
+
+ # Special flags.
+ ONLYDIR = 0x01000000 # Only watch the path if it is a directory.
+ DONT_FOLLOW = 0x02000000 # Do not follow a sym link.
+ EXCL_UNLINK = 0x04000000 # Exclude events on unlinked objects.
+ MASK_ADD = 0x20000000 # Add to the mask of an already existing watch.
+ ISDIR = 0x40000000 # Event occurred against dir.
+ ONESHOT = 0x80000000 # Only send event once.
+
+ # All events which a program can wait on.
+ ALL_EVENTS = (
+ ACCESS | MODIFY | ATTRIB | CLOSE_WRITE | CLOSE_NOWRITE | OPEN |
+ MOVED_FROM | MOVED_TO | CREATE | DELETE | DELETE_SELF | MOVE_SELF
+ )
+
+ # See <bits/inotify.h>
+ CLOEXEC = 0x80000
+ NONBLOCK = 0x800
+
+ def __init__(self, cloexec=True, nonblock=True):
+ self._init1, self._add_watch, self._rm_watch, self._read = load_inotify()
+ flags = 0
+ if cloexec:
+ flags |= self.CLOEXEC
+ if nonblock:
+ flags |= self.NONBLOCK
+ self._inotify_fd = self._init1(flags)
+ if self._inotify_fd == -1:
+ raise INotifyError(os.strerror(ctypes.get_errno()))
+
+ self._buf = ctypes.create_string_buffer(5000)
+ self.fenc = get_preferred_file_name_encoding()
+ self.hdr = struct.Struct(b'iIII')
+ # We keep a reference to os to prevent it from being deleted
+ # during interpreter shutdown, which would lead to errors in the
+ # __del__ method
+ self.os = os
+
+ def handle_error(self):
+ eno = ctypes.get_errno()
+ extra = ''
+ if eno == errno.ENOSPC:
+ extra = 'You may need to increase the inotify limits on your system, via /proc/sys/fs/inotify/max_user_*'
+ raise OSError(eno, self.os.strerror(eno) + str(extra))
+
+ def __del__(self):
+ # This method can be called during interpreter shutdown, which means we
+ # must do the absolute minimum here. Note that there could be running
+ # daemon threads that are trying to call other methods on this object.
+ try:
+ self.os.close(self._inotify_fd)
+ except (AttributeError, TypeError):
+ pass
+
+ def close(self):
+ if hasattr(self, '_inotify_fd'):
+ self.os.close(self._inotify_fd)
+ del self.os
+ del self._add_watch
+ del self._rm_watch
+ del self._inotify_fd
+
+ def read(self, get_name=True):
+ buf = []
+ while True:
+ num = self._read(self._inotify_fd, self._buf, len(self._buf))
+ if num == 0:
+ break
+ if num < 0:
+ en = ctypes.get_errno()
+ if en == errno.EAGAIN:
+ break # No more data
+ if en == errno.EINTR:
+ continue # Interrupted, try again
+ raise OSError(en, self.os.strerror(en))
+ buf.append(self._buf.raw[:num])
+ raw = b''.join(buf)
+ pos = 0
+ lraw = len(raw)
+ while lraw - pos >= self.hdr.size:
+ wd, mask, cookie, name_len = self.hdr.unpack_from(raw, pos)
+ pos += self.hdr.size
+ name = None
+ if get_name:
+ name = raw[pos:pos + name_len].rstrip(b'\0')
+ pos += name_len
+ self.process_event(wd, mask, cookie, name)
+
+ def process_event(self, *args):
+ raise NotImplementedError()
diff --git a/powerline/lib/memoize.py b/powerline/lib/memoize.py
new file mode 100644
index 0000000..cedbe45
--- /dev/null
+++ b/powerline/lib/memoize.py
@@ -0,0 +1,42 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from functools import wraps
+
+from powerline.lib.monotonic import monotonic
+
+
+def default_cache_key(**kwargs):
+ return frozenset(kwargs.items())
+
+
+class memoize(object):
+ '''Memoization decorator with timeout.'''
+ def __init__(self, timeout, cache_key=default_cache_key, cache_reg_func=None):
+ self.timeout = timeout
+ self.cache_key = cache_key
+ self.cache = {}
+ self.cache_reg_func = cache_reg_func
+
+ def __call__(self, func):
+ @wraps(func)
+ def decorated_function(**kwargs):
+ if self.cache_reg_func:
+ self.cache_reg_func(self.cache)
+ self.cache_reg_func = None
+
+ key = self.cache_key(**kwargs)
+ try:
+ cached = self.cache.get(key, None)
+ except TypeError:
+ return func(**kwargs)
+ # Handle case when time() appears to be less then cached['time'] due
+ # to clock updates. Not applicable for monotonic clock, but this
+ # case is currently rare.
+ if cached is None or not (cached['time'] < monotonic() < cached['time'] + self.timeout):
+ cached = self.cache[key] = {
+ 'result': func(**kwargs),
+ 'time': monotonic(),
+ }
+ return cached['result']
+ return decorated_function
diff --git a/powerline/lib/monotonic.py b/powerline/lib/monotonic.py
new file mode 100644
index 0000000..cd7c414
--- /dev/null
+++ b/powerline/lib/monotonic.py
@@ -0,0 +1,100 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ try:
+ # >=python-3.3, Unix
+ from time import clock_gettime
+ try:
+ # >={kernel}-sources-2.6.28
+ from time import CLOCK_MONOTONIC_RAW as CLOCK_ID
+ except ImportError:
+ from time import CLOCK_MONOTONIC as CLOCK_ID
+
+ monotonic = lambda: clock_gettime(CLOCK_ID)
+ except ImportError:
+ # >=python-3.3
+ from time import monotonic
+except ImportError:
+ import ctypes
+ import sys
+
+ try:
+ if sys.platform == 'win32':
+ # Windows only
+ GetTickCount64 = ctypes.windll.kernel32.GetTickCount64
+ GetTickCount64.restype = ctypes.c_ulonglong
+
+ def monotonic():
+ return GetTickCount64() / 1000
+
+ elif sys.platform == 'darwin':
+ # Mac OS X
+ from ctypes.util import find_library
+
+ libc_name = find_library('c')
+ if not libc_name:
+ raise OSError
+
+ libc = ctypes.CDLL(libc_name, use_errno=True)
+
+ mach_absolute_time = libc.mach_absolute_time
+ mach_absolute_time.argtypes = ()
+ mach_absolute_time.restype = ctypes.c_uint64
+
+ class mach_timebase_info_data_t(ctypes.Structure):
+ _fields_ = (
+ ('numer', ctypes.c_uint32),
+ ('denom', ctypes.c_uint32),
+ )
+ mach_timebase_info_data_p = ctypes.POINTER(mach_timebase_info_data_t)
+
+ _mach_timebase_info = libc.mach_timebase_info
+ _mach_timebase_info.argtypes = (mach_timebase_info_data_p,)
+ _mach_timebase_info.restype = ctypes.c_int
+
+ def mach_timebase_info():
+ timebase = mach_timebase_info_data_t()
+ _mach_timebase_info(ctypes.byref(timebase))
+ return (timebase.numer, timebase.denom)
+
+ timebase = mach_timebase_info()
+ factor = timebase[0] / timebase[1] * 1e-9
+
+ def monotonic():
+ return mach_absolute_time() * factor
+ else:
+ # linux only (no librt on OS X)
+ import os
+
+ # See <bits/time.h>
+ CLOCK_MONOTONIC = 1
+ CLOCK_MONOTONIC_RAW = 4
+
+ class timespec(ctypes.Structure):
+ _fields_ = (
+ ('tv_sec', ctypes.c_long),
+ ('tv_nsec', ctypes.c_long)
+ )
+ tspec = timespec()
+
+ librt = ctypes.CDLL('librt.so.1', use_errno=True)
+ clock_gettime = librt.clock_gettime
+ clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
+
+ if clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.pointer(tspec)) == 0:
+ # >={kernel}-sources-2.6.28
+ clock_id = CLOCK_MONOTONIC_RAW
+ elif clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) == 0:
+ clock_id = CLOCK_MONOTONIC
+ else:
+ raise OSError
+
+ def monotonic():
+ if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) != 0:
+ errno_ = ctypes.get_errno()
+ raise OSError(errno_, os.strerror(errno_))
+ return tspec.tv_sec + tspec.tv_nsec / 1e9
+
+ except:
+ from time import time as monotonic # NOQA
diff --git a/powerline/lib/overrides.py b/powerline/lib/overrides.py
new file mode 100644
index 0000000..3257d98
--- /dev/null
+++ b/powerline/lib/overrides.py
@@ -0,0 +1,80 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import json
+
+from powerline.lib.dict import REMOVE_THIS_KEY
+
+
+def parse_value(s):
+ '''Convert string to Python object
+
+ Rules:
+
+ * Empty string means that corresponding key should be removed from the
+ dictionary.
+ * Strings that start with a minus, digit or with some character that starts
+ JSON collection or string object are parsed as JSON.
+ * JSON special values ``null``, ``true``, ``false`` (case matters) are
+ parsed as JSON.
+ * All other values are considered to be raw strings.
+
+ :param str s: Parsed string.
+
+ :return: Python object.
+ '''
+ if not s:
+ return REMOVE_THIS_KEY
+ elif s[0] in '"{[0123456789-' or s in ('null', 'true', 'false'):
+ return json.loads(s)
+ else:
+ return s
+
+
+def keyvaluesplit(s):
+ '''Split K1.K2=VAL into K1.K2 and parsed VAL
+ '''
+ if '=' not in s:
+ raise TypeError('Option must look like option=json_value')
+ if s[0] == '_':
+ raise ValueError('Option names must not start with `_\'')
+ idx = s.index('=')
+ o = s[:idx]
+ val = parse_value(s[idx + 1:])
+ return (o, val)
+
+
+def parsedotval(s):
+ '''Parse K1.K2=VAL into {"K1":{"K2":VAL}}
+
+ ``VAL`` is processed according to rules defined in :py:func:`parse_value`.
+ '''
+ if type(s) is tuple:
+ o, val = s
+ val = parse_value(val)
+ else:
+ o, val = keyvaluesplit(s)
+
+ keys = o.split('.')
+ if len(keys) > 1:
+ r = (keys[0], {})
+ rcur = r[1]
+ for key in keys[1:-1]:
+ rcur[key] = {}
+ rcur = rcur[key]
+ rcur[keys[-1]] = val
+ return r
+ else:
+ return (o, val)
+
+
+def parse_override_var(s):
+ '''Parse a semicolon-separated list of strings into a sequence of values
+
+ Emits the same items in sequence as :py:func:`parsedotval` does.
+ '''
+ return (
+ parsedotval(item)
+ for item in s.split(';')
+ if item
+ )
diff --git a/powerline/lib/path.py b/powerline/lib/path.py
new file mode 100644
index 0000000..49ff433
--- /dev/null
+++ b/powerline/lib/path.py
@@ -0,0 +1,18 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+
+def realpath(path):
+ return os.path.abspath(os.path.realpath(path))
+
+
+def join(*components):
+ if any((isinstance(p, bytes) for p in components)):
+ return os.path.join(*[
+ p if isinstance(p, bytes) else p.encode('ascii')
+ for p in components
+ ])
+ else:
+ return os.path.join(*components)
diff --git a/powerline/lib/shell.py b/powerline/lib/shell.py
new file mode 100644
index 0000000..2082e82
--- /dev/null
+++ b/powerline/lib/shell.py
@@ -0,0 +1,133 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import os
+
+from subprocess import Popen, PIPE
+from functools import partial
+
+from powerline.lib.encoding import get_preferred_input_encoding, get_preferred_output_encoding
+
+
+if sys.platform.startswith('win32'):
+ # Prevent windows from launching consoles when calling commands
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
+ Popen = partial(Popen, creationflags=0x08000000)
+
+
+def run_cmd(pl, cmd, stdin=None, strip=True):
+ '''Run command and return its stdout, stripped
+
+ If running command fails returns None and logs failure to ``pl`` argument.
+
+ :param PowerlineLogger pl:
+ Logger used to log failures.
+ :param list cmd:
+ Command which will be run.
+ :param str stdin:
+ String passed to command. May be None.
+ :param bool strip:
+ True if the result should be stripped.
+ '''
+ try:
+ p = Popen(cmd, shell=False, stdout=PIPE, stdin=PIPE)
+ except OSError as e:
+ pl.exception('Could not execute command ({0}): {1}', e, cmd)
+ return None
+ else:
+ stdout, err = p.communicate(
+ stdin if stdin is None else stdin.encode(get_preferred_output_encoding()))
+ stdout = stdout.decode(get_preferred_input_encoding())
+ return stdout.strip() if strip else stdout
+
+
+def asrun(pl, ascript):
+ '''Run the given AppleScript and return the standard output and error.'''
+ return run_cmd(pl, ['osascript', '-'], ascript)
+
+
+def readlines(cmd, cwd):
+ '''Run command and read its output, line by line
+
+ :param list cmd:
+ Command which will be run.
+ :param str cwd:
+ Working directory of the command which will be run.
+ '''
+ p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd)
+ encoding = get_preferred_input_encoding()
+ p.stderr.close()
+ with p.stdout:
+ for line in p.stdout:
+ yield line[:-1].decode(encoding)
+
+
+try:
+ from shutil import which
+except ImportError:
+ # shutil.which was added in python-3.3. Here is what was added:
+ # Lib/shutil.py, commit 5abe28a9c8fe701ba19b1db5190863384e96c798
+ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
+ '''Given a command, mode, and a PATH string, return the path which
+ conforms to the given mode on the PATH, or None if there is no such
+ file.
+
+ ``mode`` defaults to os.F_OK | os.X_OK. ``path`` defaults to the result
+ of ``os.environ.get('PATH')``, or can be overridden with a custom search
+ path.
+ '''
+ # Check that a given file can be accessed with the correct mode.
+ # Additionally check that `file` is not a directory, as on Windows
+ # directories pass the os.access check.
+ def _access_check(fn, mode):
+ return (
+ os.path.exists(fn)
+ and os.access(fn, mode)
+ and not os.path.isdir(fn)
+ )
+
+ # If we’re given a path with a directory part, look it up directly rather
+ # than referring to PATH directories. This includes checking relative to the
+ # current directory, e.g. ./script
+ if os.path.dirname(cmd):
+ if _access_check(cmd, mode):
+ return cmd
+ return None
+
+ if path is None:
+ path = os.environ.get('PATH', os.defpath)
+ if not path:
+ return None
+ path = path.split(os.pathsep)
+
+ if sys.platform == 'win32':
+ # The current directory takes precedence on Windows.
+ if os.curdir not in path:
+ path.insert(0, os.curdir)
+
+ # PATHEXT is necessary to check on Windows.
+ pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
+ # See if the given file matches any of the expected path extensions.
+ # This will allow us to short circuit when given 'python.exe'.
+ # If it does match, only test that one, otherwise we have to try
+ # others.
+ if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
+ files = [cmd]
+ else:
+ files = [cmd + ext for ext in pathext]
+ else:
+ # On other platforms you don’t have things like PATHEXT to tell you
+ # what file suffixes are executable, so just pass on cmd as-is.
+ files = [cmd]
+
+ seen = set()
+ for dir in path:
+ normdir = os.path.normcase(dir)
+ if normdir not in seen:
+ seen.add(normdir)
+ for thefile in files:
+ name = os.path.join(dir, thefile)
+ if _access_check(name, mode):
+ return name
+ return None
diff --git a/powerline/lib/threaded.py b/powerline/lib/threaded.py
new file mode 100644
index 0000000..e5a6b3e
--- /dev/null
+++ b/powerline/lib/threaded.py
@@ -0,0 +1,262 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from threading import Thread, Lock, Event
+from types import MethodType
+
+from powerline.lib.monotonic import monotonic
+from powerline.segments import Segment
+
+
+class MultiRunnedThread(object):
+ daemon = True
+
+ def __init__(self):
+ self.thread = None
+
+ def is_alive(self):
+ return self.thread and self.thread.is_alive()
+
+ def start(self):
+ self.shutdown_event.clear()
+ self.thread = Thread(target=self.run)
+ self.thread.daemon = self.daemon
+ self.thread.start()
+
+ def join(self, *args, **kwargs):
+ if self.thread:
+ return self.thread.join(*args, **kwargs)
+ return None
+
+
+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
+ self.crashed = False
+ self.crashed_value = None
+ self.update_value = None
+ self.updated = False
+
+ def __call__(self, pl, update_first=True, **kwargs):
+ if self.run_once:
+ self.pl = pl
+ self.set_state(**kwargs)
+ update_value = self.get_update_value(True)
+ elif not self.is_alive():
+ # Without this we will not have to wait long until receiving bug “I
+ # opened vim, but branch information is only shown after I move
+ # cursor”.
+ #
+ # If running once .update() is called in __call__.
+ self.start()
+ update_value = self.get_update_value(self.do_update_first)
+ else:
+ update_value = self.get_update_value(not self.updated)
+
+ if self.crashed:
+ return self.crashed_value
+
+ return self.render(update_value, update_first=update_first, pl=pl, **kwargs)
+
+ def set_update_value(self):
+ try:
+ self.update_value = self.update(self.update_value)
+ except Exception as e:
+ self.exception('Exception while updating: {0}', str(e))
+ self.crashed = True
+ except KeyboardInterrupt:
+ self.warn('Caught keyboard interrupt while updating')
+ self.crashed = True
+ else:
+ self.crashed = False
+ self.updated = True
+
+ def get_update_value(self, update=False):
+ if update:
+ self.set_update_value()
+ return self.update_value
+
+ def run(self):
+ if self.do_update_first:
+ start_time = monotonic()
+ while True:
+ self.shutdown_event.wait(max(self.interval - (monotonic() - start_time), self.min_sleep_time))
+ if self.shutdown_event.is_set():
+ break
+ start_time = monotonic()
+ self.set_update_value()
+ else:
+ while not self.shutdown_event.is_set():
+ start_time = monotonic()
+ self.set_update_value()
+ self.shutdown_event.wait(max(self.interval - (monotonic() - start_time), self.min_sleep_time))
+
+ def shutdown(self):
+ self.shutdown_event.set()
+ if self.daemon and self.is_alive():
+ # Give the worker thread a chance to shutdown, but don’t block for
+ # too long
+ self.join(0.01)
+
+ def set_interval(self, interval=None):
+ # Allowing “interval” keyword in configuration.
+ # Note: Here **kwargs is needed to support foreign data, in subclasses
+ # it can be seen in a number of places in order to support
+ # .set_interval().
+ interval = interval or getattr(self, 'interval')
+ self.interval = interval
+
+ def set_state(self, interval=None, update_first=True, shutdown_event=None, **kwargs):
+ self.set_interval(interval)
+ self.shutdown_event = shutdown_event or Event()
+ self.do_update_first = update_first and self.update_first
+ self.updated = self.updated or (not self.do_update_first)
+
+ def startup(self, pl, **kwargs):
+ self.run_once = False
+ self.pl = pl
+ self.daemon = pl.use_daemon_threads
+
+ self.set_state(**kwargs)
+
+ if not self.is_alive():
+ self.start()
+
+ def critical(self, *args, **kwargs):
+ self.pl.critical(prefix=self.__class__.__name__, *args, **kwargs)
+
+ def exception(self, *args, **kwargs):
+ self.pl.exception(prefix=self.__class__.__name__, *args, **kwargs)
+
+ def info(self, *args, **kwargs):
+ self.pl.info(prefix=self.__class__.__name__, *args, **kwargs)
+
+ def error(self, *args, **kwargs):
+ self.pl.error(prefix=self.__class__.__name__, *args, **kwargs)
+
+ def warn(self, *args, **kwargs):
+ self.pl.warn(prefix=self.__class__.__name__, *args, **kwargs)
+
+ 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),)
+
+ _omitted_args = {
+ 'render': (0,),
+ 'set_state': ('shutdown_event',),
+ }
+
+ def omitted_args(self, name, method):
+ ret = self._omitted_args.get(name, ())
+ if isinstance(getattr(self, name, None), MethodType):
+ ret = tuple((i + 1 if isinstance(i, int) else i for i in ret))
+ return ret
+
+
+class KwThreadedSegment(ThreadedSegment):
+ update_first = True
+
+ argmethods = ('render', 'set_state', 'key', 'render_one')
+
+ def __init__(self):
+ super(KwThreadedSegment, self).__init__()
+ self.updated = True
+ self.update_value = ({}, set())
+ self.write_lock = Lock()
+ self.new_queries = []
+
+ @staticmethod
+ def key(**kwargs):
+ return frozenset(kwargs.items())
+
+ def render(self, update_value, update_first, key=None, after_update=False, **kwargs):
+ queries, crashed = update_value
+ if key is None:
+ key = self.key(**kwargs)
+ if key in crashed:
+ return self.crashed_value
+
+ try:
+ update_state = queries[key][1]
+ except KeyError:
+ with self.write_lock:
+ self.new_queries.append(key)
+ if self.do_update_first or self.run_once:
+ if after_update:
+ self.error('internal error: value was not computed even though update_first was set')
+ update_state = None
+ else:
+ return self.render(
+ update_value=self.get_update_value(True),
+ update_first=False,
+ key=key,
+ after_update=True,
+ **kwargs
+ )
+ else:
+ update_state = None
+
+ return self.render_one(update_state, **kwargs)
+
+ def update_one(self, crashed, updates, key):
+ try:
+ updates[key] = (monotonic(), self.compute_state(key))
+ except Exception as e:
+ self.exception('Exception while computing state for {0!r}: {1}', key, str(e))
+ crashed.add(key)
+ except KeyboardInterrupt:
+ self.warn('Interrupt while computing state for {0!r}', key)
+ crashed.add(key)
+
+ def update(self, old_update_value):
+ updates = {}
+ crashed = set()
+ update_value = (updates, crashed)
+ queries = old_update_value[0]
+
+ new_queries = self.new_queries
+ with self.write_lock:
+ self.new_queries = []
+
+ for key, (last_query_time, state) in queries.items():
+ if last_query_time < monotonic() < last_query_time + self.interval:
+ updates[key] = (last_query_time, state)
+ else:
+ self.update_one(crashed, updates, key)
+
+ for key in new_queries:
+ self.update_one(crashed, updates, key)
+
+ return update_value
+
+ def set_state(self, interval=None, update_first=True, shutdown_event=None, **kwargs):
+ self.set_interval(interval)
+ self.do_update_first = update_first and self.update_first
+ self.shutdown_event = shutdown_event or Event()
+
+ @staticmethod
+ def render_one(update_state, **kwargs):
+ return update_state
+
+ _omitted_args = {
+ 'render': ('update_value', 'key', 'after_update'),
+ 'set_state': ('shutdown_event',),
+ 'render_one': (0,),
+ }
diff --git a/powerline/lib/unicode.py b/powerline/lib/unicode.py
new file mode 100644
index 0000000..eeae387
--- /dev/null
+++ b/powerline/lib/unicode.py
@@ -0,0 +1,283 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import codecs
+
+from unicodedata import east_asian_width, combining
+
+from powerline.lib.encoding import get_preferred_output_encoding
+
+
+try:
+ from __builtin__ import unicode
+except ImportError:
+ unicode = str
+
+
+try:
+ from __builtin__ import unichr
+except ImportError:
+ unichr = chr
+
+
+if sys.maxunicode < 0x10FFFF:
+ _unichr = unichr
+
+ def unichr(ch):
+ if ch <= sys.maxunicode:
+ return _unichr(ch)
+ else:
+ ch -= 0x10000
+ return _unichr((ch >> 10) + 0xD800) + _unichr((ch & ((1 << 10) - 1)) + 0xDC00)
+
+
+def u(s):
+ '''Return unicode instance assuming UTF-8 encoded string.
+ '''
+ if type(s) is unicode:
+ return s
+ else:
+ return unicode(s, 'utf-8')
+
+
+if sys.version_info < (3,):
+ def tointiter(s):
+ '''Convert a byte string to the sequence of integers
+ '''
+ return (ord(c) for c in s)
+else:
+ def tointiter(s):
+ '''Convert a byte string to the sequence of integers
+ '''
+ return iter(s)
+
+
+def powerline_decode_error(e):
+ if not isinstance(e, UnicodeDecodeError):
+ raise NotImplementedError
+ return (''.join((
+ '<{0:02X}>'.format(c)
+ for c in tointiter(e.object[e.start:e.end])
+ )), e.end)
+
+
+codecs.register_error('powerline_decode_error', powerline_decode_error)
+
+
+last_swe_idx = 0
+
+
+def register_strwidth_error(strwidth):
+ '''Create new encode errors handling method similar to ``replace``
+
+ Like ``replace`` this method uses question marks in place of the characters
+ that cannot be represented in the requested encoding. Unlike ``replace`` the
+ amount of question marks is identical to the amount of display cells
+ offending character occupies. Thus encoding ``…`` (U+2026, HORIZONTAL
+ ELLIPSIS) to ``latin1`` will emit one question mark, but encoding ``A``
+ (U+FF21, FULLWIDTH LATIN CAPITAL LETTER A) will emit two question marks.
+
+ Since width of some characters depends on the terminal settings and
+ powerline knows how to respect them a single error handling method cannot be
+ used. Instead of it the generator function is used which takes ``strwidth``
+ function (function that knows how to compute string width respecting all
+ needed settings) and emits new error handling method name.
+
+ :param function strwidth:
+ Function that computs string width measured in display cells the string
+ occupies when displayed.
+
+ :return: New error handling method name.
+ '''
+ global last_swe_idx
+ last_swe_idx += 1
+
+ def powerline_encode_strwidth_error(e):
+ if not isinstance(e, UnicodeEncodeError):
+ raise NotImplementedError
+ return ('?' * strwidth(e.object[e.start:e.end]), e.end)
+
+ ename = 'powerline_encode_strwidth_error_{0}'.format(last_swe_idx)
+ codecs.register_error(ename, powerline_encode_strwidth_error)
+ return ename
+
+
+def out_u(s):
+ '''Return unicode string suitable for displaying
+
+ Unlike other functions assumes get_preferred_output_encoding() first. Unlike
+ u() does not throw exceptions for invalid unicode strings. Unlike
+ safe_unicode() does throw an exception if object is not a string.
+ '''
+ if isinstance(s, unicode):
+ return s
+ elif isinstance(s, bytes):
+ return unicode(s, get_preferred_output_encoding(), 'powerline_decode_error')
+ else:
+ raise TypeError('Expected unicode or bytes instance, got {0}'.format(repr(type(s))))
+
+
+def safe_unicode(s):
+ '''Return unicode instance without raising an exception.
+
+ Order of assumptions:
+ * ASCII string or unicode object
+ * UTF-8 string
+ * Object with __str__() or __repr__() method that returns UTF-8 string or
+ unicode object (depending on python version)
+ * String in powerline.lib.encoding.get_preferred_output_encoding() encoding
+ * If everything failed use safe_unicode on last exception with which
+ everything failed
+ '''
+ try:
+ try:
+ if type(s) is bytes:
+ return unicode(s, 'ascii')
+ else:
+ return unicode(s)
+ except UnicodeDecodeError:
+ try:
+ return unicode(s, 'utf-8')
+ except TypeError:
+ return unicode(str(s), 'utf-8')
+ except UnicodeDecodeError:
+ return unicode(s, get_preferred_output_encoding())
+ except Exception as e:
+ return safe_unicode(e)
+
+
+class FailedUnicode(unicode):
+ '''Builtin ``unicode`` subclass indicating fatal error
+
+ If your code for some reason wants to determine whether `.render()` method
+ failed it should check returned string for being a FailedUnicode instance.
+ Alternatively you could subclass Powerline and override `.render()` method
+ to do what you like in place of catching the exception and returning
+ FailedUnicode.
+ '''
+ pass
+
+
+if sys.version_info < (3,):
+ def string(s):
+ if type(s) is not str:
+ return s.encode('utf-8')
+ else:
+ return s
+else:
+ def string(s):
+ if type(s) is not str:
+ return s.decode('utf-8')
+ else:
+ return s
+
+
+string.__doc__ = (
+ '''Transform ``unicode`` or ``bytes`` object into ``str`` object
+
+ On Python-2 this encodes ``unicode`` to ``bytes`` (which is ``str``) using
+ UTF-8 encoding; on Python-3 this decodes ``bytes`` to ``unicode`` (which is
+ ``str``) using UTF-8 encoding.
+
+ Useful for functions that expect an ``str`` object in both unicode versions,
+ not caring about the semantic differences between them in Python-2 and
+ Python-3.
+ '''
+)
+
+
+def surrogate_pair_to_character(high, low):
+ '''Transform a pair of surrogate codepoints to one codepoint
+ '''
+ return 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00)
+
+
+_strwidth_documentation = (
+ '''Compute string width in display cells
+
+ {0}
+
+ :param dict width_data:
+ Dictionary which maps east_asian_width property values to strings
+ lengths. It is expected to contain the following keys and values (from
+ `East Asian Width annex <http://www.unicode.org/reports/tr11/>`_):
+
+ === ====== ===========================================================
+ Key Value Description
+ === ====== ===========================================================
+ F 2 Fullwidth: all characters that are defined as Fullwidth in
+ the Unicode Standard [Unicode] by having a compatibility
+ decomposition of type <wide> to characters elsewhere in the
+ Unicode Standard that are implicitly narrow but unmarked.
+ H 1 Halfwidth: all characters that are explicitly defined as
+ Halfwidth in the Unicode Standard by having a compatibility
+ decomposition of type <narrow> to characters elsewhere in
+ the Unicode Standard that are implicitly wide but unmarked,
+ plus U+20A9 ₩ WON SIGN.
+ W 2 Wide: all other characters that are always wide. These
+ characters occur only in the context of East Asian
+ typography where they are wide characters (such as the
+ Unified Han Ideographs or Squared Katakana Symbols). This
+ category includes characters that have explicit halfwidth
+ counterparts.
+ Na 1 Narrow: characters that are always narrow and have explicit
+ fullwidth or wide counterparts. These characters are
+ implicitly narrow in East Asian typography and legacy
+ character sets because they have explicit fullwidth or wide
+ counterparts. All of ASCII is an example of East Asian
+ Narrow characters.
+ A 1 or 2 Ambiguous: characters that may sometimes be wide and
+ sometimes narrow. Ambiguous characters require additional
+ information not contained in the character code to further
+ resolve their width. This information is usually defined in
+ terminal setting that should in turn respect glyphs widths
+ in used fonts. Also see :ref:`ambiwidth configuration
+ option <config-common-ambiwidth>`.
+ N 1 Neutral characters: character that does not occur in legacy
+ East Asian character sets.
+ === ====== ===========================================================
+
+ :param unicode string:
+ String whose width will be calculated.
+
+ :return: unsigned integer.''')
+
+
+def strwidth_ucs_4(width_data, string):
+ return sum(((
+ (
+ 0
+ ) if combining(symbol) else (
+ width_data[east_asian_width(symbol)]
+ )
+ ) for symbol in string))
+
+
+strwidth_ucs_4.__doc__ = _strwidth_documentation.format(
+ '''This version of function expects that characters above 0xFFFF are
+ represented using one symbol. This is only the case in UCS-4 Python builds.
+
+ .. note:
+ Even in UCS-4 Python builds it is possible to represent characters above
+ 0xFFFF using surrogate pairs. Characters represented this way are not
+ supported.''')
+
+
+def strwidth_ucs_2(width_data, string):
+ return sum(((
+ (
+ width_data[east_asian_width(string[i - 1] + symbol)]
+ ) if 0xDC00 <= ord(symbol) <= 0xDFFF else (
+ 0
+ ) if combining(symbol) or 0xD800 <= ord(symbol) <= 0xDBFF else (
+ width_data[east_asian_width(symbol)]
+ )
+ ) for i, symbol in enumerate(string)))
+
+
+strwidth_ucs_2.__doc__ = _strwidth_documentation.format(
+ '''This version of function expects that characters above 0xFFFF are
+ represented using two symbols forming a surrogate pair, which is the only
+ option in UCS-2 Python builds. It still works correctly in UCS-4 Python
+ builds, but is slower then its UCS-4 counterpart.''')
diff --git a/powerline/lib/url.py b/powerline/lib/url.py
new file mode 100644
index 0000000..f25919c
--- /dev/null
+++ b/powerline/lib/url.py
@@ -0,0 +1,17 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ from urllib.error import HTTPError # NOQA
+ from urllib.request import urlopen # NOQA
+ from urllib.parse import urlencode as urllib_urlencode # NOQA
+except ImportError:
+ from urllib2 import urlopen, HTTPError # NOQA
+ from urllib import urlencode as urllib_urlencode # NOQA
+
+
+def urllib_read(url):
+ try:
+ return urlopen(url, timeout=10).read().decode('utf-8')
+ except HTTPError:
+ return
diff --git a/powerline/lib/vcs/__init__.py b/powerline/lib/vcs/__init__.py
new file mode 100644
index 0000000..f862c6b
--- /dev/null
+++ b/powerline/lib/vcs/__init__.py
@@ -0,0 +1,276 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import errno
+
+from threading import Lock
+from collections import defaultdict
+
+from powerline.lib.watcher import create_tree_watcher
+from powerline.lib.unicode import out_u
+from powerline.lib.path import join
+
+
+def generate_directories(path):
+ if os.path.isdir(path):
+ yield path
+ while True:
+ if os.path.ismount(path):
+ break
+ old_path = path
+ path = os.path.dirname(path)
+ if path == old_path or not path:
+ break
+ yield path
+
+
+_file_watcher = None
+
+
+def file_watcher(create_watcher):
+ global _file_watcher
+ if _file_watcher is None:
+ _file_watcher = create_watcher()
+ return _file_watcher
+
+
+_branch_watcher = None
+
+
+def branch_watcher(create_watcher):
+ global _branch_watcher
+ if _branch_watcher is None:
+ _branch_watcher = create_watcher()
+ return _branch_watcher
+
+
+branch_name_cache = {}
+branch_lock = Lock()
+file_status_lock = Lock()
+
+
+def get_branch_name(directory, config_file, get_func, create_watcher):
+ global branch_name_cache
+ with branch_lock:
+ # Check if the repo directory was moved/deleted
+ fw = branch_watcher(create_watcher)
+ is_watched = fw.is_watching(directory)
+ try:
+ changed = fw(directory)
+ except OSError as e:
+ if getattr(e, 'errno', None) != errno.ENOENT:
+ raise
+ changed = True
+ if changed:
+ branch_name_cache.pop(config_file, None)
+ # Remove the watches for this repo
+ if is_watched:
+ fw.unwatch(directory)
+ fw.unwatch(config_file)
+ else:
+ # Check if the config file has changed
+ try:
+ changed = fw(config_file)
+ except OSError as e:
+ if getattr(e, 'errno', None) != errno.ENOENT:
+ raise
+ # Config file does not exist (happens for mercurial)
+ if config_file not in branch_name_cache:
+ branch_name_cache[config_file] = out_u(get_func(directory, config_file))
+ if changed:
+ # Config file has changed or was not tracked
+ branch_name_cache[config_file] = out_u(get_func(directory, config_file))
+ return branch_name_cache[config_file]
+
+
+class FileStatusCache(dict):
+ def __init__(self):
+ self.dirstate_map = defaultdict(set)
+ self.ignore_map = defaultdict(set)
+ self.keypath_ignore_map = {}
+
+ def update_maps(self, keypath, directory, dirstate_file, ignore_file_name, extra_ignore_files):
+ parent = keypath
+ ignore_files = set()
+ while parent != directory:
+ nparent = os.path.dirname(keypath)
+ if nparent == parent:
+ break
+ parent = nparent
+ ignore_files.add(join(parent, ignore_file_name))
+ for f in extra_ignore_files:
+ ignore_files.add(f)
+ self.keypath_ignore_map[keypath] = ignore_files
+ for ignf in ignore_files:
+ self.ignore_map[ignf].add(keypath)
+ self.dirstate_map[dirstate_file].add(keypath)
+
+ def invalidate(self, dirstate_file=None, ignore_file=None):
+ for keypath in self.dirstate_map[dirstate_file]:
+ self.pop(keypath, None)
+ for keypath in self.ignore_map[ignore_file]:
+ self.pop(keypath, None)
+
+ def ignore_files(self, keypath):
+ for ignf in self.keypath_ignore_map[keypath]:
+ yield ignf
+
+
+file_status_cache = FileStatusCache()
+
+
+def get_file_status(directory, dirstate_file, file_path, ignore_file_name, get_func, create_watcher, extra_ignore_files=()):
+ global file_status_cache
+ keypath = file_path if os.path.isabs(file_path) else join(directory, file_path)
+ file_status_cache.update_maps(keypath, directory, dirstate_file, ignore_file_name, extra_ignore_files)
+
+ with file_status_lock:
+ # Optimize case of keypath not being cached
+ if keypath not in file_status_cache:
+ file_status_cache[keypath] = ans = get_func(directory, file_path)
+ return ans
+
+ # Check if any relevant files have changed
+ file_changed = file_watcher(create_watcher)
+ changed = False
+ # Check if dirstate has changed
+ try:
+ changed = file_changed(dirstate_file)
+ except OSError as e:
+ if getattr(e, 'errno', None) != errno.ENOENT:
+ raise
+ # The .git index file does not exist for a new git repo
+ return get_func(directory, file_path)
+
+ if changed:
+ # Remove all cached values for files that depend on this
+ # dirstate_file
+ file_status_cache.invalidate(dirstate_file=dirstate_file)
+ else:
+ # Check if the file itself has changed
+ try:
+ changed ^= file_changed(keypath)
+ except OSError as e:
+ if getattr(e, 'errno', None) != errno.ENOENT:
+ raise
+ # Do not call get_func again for a non-existent file
+ if keypath not in file_status_cache:
+ file_status_cache[keypath] = get_func(directory, file_path)
+ return file_status_cache[keypath]
+
+ if changed:
+ file_status_cache.pop(keypath, None)
+ else:
+ # Check if one of the ignore files has changed
+ for ignf in file_status_cache.ignore_files(keypath):
+ try:
+ changed ^= file_changed(ignf)
+ except OSError as e:
+ if getattr(e, 'errno', None) != errno.ENOENT:
+ raise
+ if changed:
+ # Invalidate cache for all files that might be affected
+ # by this ignore file
+ file_status_cache.invalidate(ignore_file=ignf)
+ break
+
+ try:
+ return file_status_cache[keypath]
+ except KeyError:
+ file_status_cache[keypath] = ans = get_func(directory, file_path)
+ return ans
+
+
+class TreeStatusCache(dict):
+ def __init__(self, pl):
+ self.tw = create_tree_watcher(pl)
+ self.pl = pl
+
+ def cache_and_get(self, key, status):
+ ans = self.get(key, self)
+ if ans is self:
+ ans = self[key] = status()
+ return ans
+
+ def __call__(self, repo):
+ key = repo.directory
+ try:
+ if self.tw(key, ignore_event=getattr(repo, 'ignore_event', None)):
+ self.pop(key, None)
+ except OSError as e:
+ self.pl.warn('Failed to check {0} for changes, with error: {1}', key, str(e))
+ return self.cache_and_get(key, repo.status)
+
+
+_tree_status_cache = None
+
+
+def tree_status(repo, pl):
+ global _tree_status_cache
+ if _tree_status_cache is None:
+ _tree_status_cache = TreeStatusCache(pl)
+ return _tree_status_cache(repo)
+
+
+vcs_props = (
+ ('git', '.git', os.path.exists),
+ ('mercurial', '.hg', os.path.isdir),
+ ('bzr', '.bzr', os.path.isdir),
+)
+
+
+vcs_props_bytes = [
+ (vcs, vcs_dir.encode('ascii'), check)
+ for vcs, vcs_dir, check in vcs_props
+]
+
+
+def guess(path, create_watcher):
+ for directory in generate_directories(path):
+ for vcs, vcs_dir, check in (vcs_props_bytes if isinstance(path, bytes) else vcs_props):
+ repo_dir = os.path.join(directory, vcs_dir)
+ if check(repo_dir):
+ if os.path.isdir(repo_dir) and not os.access(repo_dir, os.X_OK):
+ continue
+ try:
+ if vcs not in globals():
+ globals()[vcs] = getattr(__import__(str('powerline.lib.vcs'), fromlist=[str(vcs)]), str(vcs))
+ return globals()[vcs].Repository(directory, create_watcher)
+ except:
+ pass
+ return None
+
+
+def get_fallback_create_watcher():
+ from powerline.lib.watcher import create_file_watcher
+ from powerline import get_fallback_logger
+ from functools import partial
+ return partial(create_file_watcher, get_fallback_logger(), 'auto')
+
+
+def debug():
+ '''Test run guess(), repo.branch() and repo.status()
+
+ To use::
+ python -c 'from powerline.lib.vcs import debug; debug()' some_file_to_watch.
+ '''
+ import sys
+ dest = sys.argv[-1]
+ repo = guess(os.path.abspath(dest), get_fallback_create_watcher)
+ if repo is None:
+ print ('%s is not a recognized vcs repo' % dest)
+ raise SystemExit(1)
+ print ('Watching %s' % dest)
+ print ('Press Ctrl-C to exit.')
+ try:
+ while True:
+ if os.path.isdir(dest):
+ print ('Branch name: %s Status: %s' % (repo.branch(), repo.status()))
+ else:
+ print ('File status: %s' % repo.status(dest))
+ raw_input('Press Enter to check again: ')
+ except KeyboardInterrupt:
+ pass
+ except EOFError:
+ pass
diff --git a/powerline/lib/vcs/bzr.py b/powerline/lib/vcs/bzr.py
new file mode 100644
index 0000000..e47d8b2
--- /dev/null
+++ b/powerline/lib/vcs/bzr.py
@@ -0,0 +1,108 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import re
+
+from io import StringIO
+
+from bzrlib import (workingtree, status, library_state, trace, ui)
+
+from powerline.lib.vcs import get_branch_name, get_file_status
+from powerline.lib.path import join
+from powerline.lib.encoding import get_preferred_file_contents_encoding
+
+
+class CoerceIO(StringIO):
+ def write(self, arg):
+ if isinstance(arg, bytes):
+ arg = arg.decode(get_preferred_file_contents_encoding(), 'replace')
+ return super(CoerceIO, self).write(arg)
+
+
+nick_pat = re.compile(br'nickname\s*=\s*(.+)')
+
+
+def branch_name_from_config_file(directory, config_file):
+ ans = None
+ try:
+ with open(config_file, 'rb') as f:
+ for line in f:
+ m = nick_pat.match(line)
+ if m is not None:
+ ans = m.group(1).strip().decode(get_preferred_file_contents_encoding(), 'replace')
+ break
+ except Exception:
+ pass
+ return ans or os.path.basename(directory)
+
+
+state = None
+
+
+class Repository(object):
+ def __init__(self, directory, create_watcher):
+ self.directory = os.path.abspath(directory)
+ self.create_watcher = create_watcher
+
+ def status(self, path=None):
+ '''Return status of repository or file.
+
+ Without file argument: returns status of the repository:
+
+ :'D?': dirty (tracked modified files: added, removed, deleted, modified),
+ :'?U': untracked-dirty (added, but not tracked files)
+ :None: clean (status is empty)
+
+ With file argument: returns status of this file: The status codes are
+ those returned by bzr status -S
+ '''
+ if path is not None:
+ return get_file_status(
+ directory=self.directory,
+ dirstate_file=join(self.directory, '.bzr', 'checkout', 'dirstate'),
+ file_path=path,
+ ignore_file_name='.bzrignore',
+ get_func=self.do_status,
+ create_watcher=self.create_watcher,
+ )
+ return self.do_status(self.directory, path)
+
+ def do_status(self, directory, path):
+ try:
+ return self._status(self.directory, path)
+ except Exception:
+ pass
+
+ def _status(self, directory, path):
+ global state
+ if state is None:
+ state = library_state.BzrLibraryState(ui=ui.SilentUIFactory, trace=trace.DefaultConfig())
+ buf = CoerceIO()
+ w = workingtree.WorkingTree.open(directory)
+ status.show_tree_status(w, specific_files=[path] if path else None, to_file=buf, short=True)
+ raw = buf.getvalue()
+ if not raw.strip():
+ return
+ if path:
+ ans = raw[:2]
+ if ans == 'I ': # Ignored
+ ans = None
+ return ans
+ dirtied = untracked = ' '
+ for line in raw.splitlines():
+ if len(line) > 1 and line[1] in 'ACDMRIN':
+ dirtied = 'D'
+ elif line and line[0] == '?':
+ untracked = 'U'
+ ans = dirtied + untracked
+ return ans if ans.strip() else None
+
+ def branch(self):
+ config_file = join(self.directory, '.bzr', 'branch', 'branch.conf')
+ return get_branch_name(
+ directory=self.directory,
+ config_file=config_file,
+ get_func=branch_name_from_config_file,
+ create_watcher=self.create_watcher,
+ )
diff --git a/powerline/lib/vcs/git.py b/powerline/lib/vcs/git.py
new file mode 100644
index 0000000..bebc311
--- /dev/null
+++ b/powerline/lib/vcs/git.py
@@ -0,0 +1,208 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import re
+
+from powerline.lib.vcs import get_branch_name, get_file_status
+from powerline.lib.shell import readlines
+from powerline.lib.path import join
+from powerline.lib.encoding import (get_preferred_file_name_encoding,
+ get_preferred_file_contents_encoding)
+from powerline.lib.shell import which
+
+
+_ref_pat = re.compile(br'ref:\s*refs/heads/(.+)')
+
+
+def branch_name_from_config_file(directory, config_file):
+ try:
+ with open(config_file, 'rb') as f:
+ raw = f.read()
+ except EnvironmentError:
+ return os.path.basename(directory)
+ m = _ref_pat.match(raw)
+ if m is not None:
+ return m.group(1).decode(get_preferred_file_contents_encoding(), 'replace')
+ return raw[:7]
+
+
+def git_directory(directory):
+ path = join(directory, '.git')
+ if os.path.isfile(path):
+ with open(path, 'rb') as f:
+ raw = f.read()
+ if not raw.startswith(b'gitdir: '):
+ raise IOError('invalid gitfile format')
+ raw = raw[8:]
+ if raw[-1:] == b'\n':
+ raw = raw[:-1]
+ if not isinstance(path, bytes):
+ raw = raw.decode(get_preferred_file_name_encoding())
+ if not raw:
+ raise IOError('no path in gitfile')
+ return os.path.abspath(os.path.join(directory, raw))
+ else:
+ return path
+
+
+class GitRepository(object):
+ __slots__ = ('directory', 'create_watcher')
+
+ def __init__(self, directory, create_watcher):
+ self.directory = os.path.abspath(directory)
+ self.create_watcher = create_watcher
+
+ def status(self, path=None):
+ '''Return status of repository or file.
+
+ Without file argument: returns status of the repository:
+
+ :First column: working directory status (D: dirty / space)
+ :Second column: index status (I: index dirty / space)
+ :Third column: presence of untracked files (U: untracked files / space)
+ :None: repository clean
+
+ With file argument: returns status of this file. Output is
+ equivalent to the first two columns of ``git status --porcelain``
+ (except for merge statuses as they are not supported by libgit2).
+ '''
+ if path:
+ gitd = git_directory(self.directory)
+ # We need HEAD as without it using fugitive to commit causes the
+ # current file’s status (and only the current file) to not be updated
+ # for some reason I cannot be bothered to figure out.
+ return get_file_status(
+ directory=self.directory,
+ dirstate_file=join(gitd, 'index'),
+ file_path=path,
+ ignore_file_name='.gitignore',
+ get_func=self.do_status,
+ create_watcher=self.create_watcher,
+ extra_ignore_files=tuple(join(gitd, x) for x in ('logs/HEAD', 'info/exclude')),
+ )
+ return self.do_status(self.directory, path)
+
+ def branch(self):
+ directory = git_directory(self.directory)
+ head = join(directory, 'HEAD')
+ return get_branch_name(
+ directory=directory,
+ config_file=head,
+ get_func=branch_name_from_config_file,
+ create_watcher=self.create_watcher,
+ )
+
+
+try:
+ import pygit2 as git
+
+ class Repository(GitRepository):
+ @staticmethod
+ def ignore_event(path, name):
+ return False
+
+ def stash(self):
+ try:
+ stashref = git.Repository(git_directory(self.directory)).lookup_reference('refs/stash')
+ except KeyError:
+ return 0
+ return sum(1 for _ in stashref.log())
+
+ def do_status(self, directory, path):
+ if path:
+ try:
+ status = git.Repository(directory).status_file(path)
+ except (KeyError, ValueError):
+ return None
+
+ if status == git.GIT_STATUS_CURRENT:
+ return None
+ else:
+ if status & git.GIT_STATUS_WT_NEW:
+ return '??'
+ if status & git.GIT_STATUS_IGNORED:
+ return '!!'
+
+ if status & git.GIT_STATUS_INDEX_NEW:
+ index_status = 'A'
+ elif status & git.GIT_STATUS_INDEX_DELETED:
+ index_status = 'D'
+ elif status & git.GIT_STATUS_INDEX_MODIFIED:
+ index_status = 'M'
+ else:
+ index_status = ' '
+
+ if status & git.GIT_STATUS_WT_DELETED:
+ wt_status = 'D'
+ elif status & git.GIT_STATUS_WT_MODIFIED:
+ wt_status = 'M'
+ else:
+ wt_status = ' '
+
+ return index_status + wt_status
+ else:
+ wt_column = ' '
+ index_column = ' '
+ untracked_column = ' '
+ for status in git.Repository(directory).status().values():
+ if status & git.GIT_STATUS_WT_NEW:
+ untracked_column = 'U'
+ continue
+
+ if status & (git.GIT_STATUS_WT_DELETED | git.GIT_STATUS_WT_MODIFIED):
+ wt_column = 'D'
+
+ if status & (
+ git.GIT_STATUS_INDEX_NEW
+ | git.GIT_STATUS_INDEX_MODIFIED
+ | git.GIT_STATUS_INDEX_DELETED
+ ):
+ index_column = 'I'
+ r = wt_column + index_column + untracked_column
+ return r if r != ' ' else None
+except ImportError:
+ class Repository(GitRepository):
+ def __init__(self, *args, **kwargs):
+ if not which('git'):
+ raise OSError('git executable is not available')
+ super(Repository, self).__init__(*args, **kwargs)
+
+ @staticmethod
+ def ignore_event(path, name):
+ # Ignore changes to the index.lock file, since they happen
+ # frequently and don't indicate an actual change in the working tree
+ # status
+ return path.endswith('.git') and name == 'index.lock'
+
+ def _gitcmd(self, directory, *args):
+ return readlines(('git',) + args, directory)
+
+ def stash(self):
+ return sum(1 for _ in self._gitcmd(self.directory, '--no-optional-locks', 'stash', 'list'))
+
+ def do_status(self, directory, path):
+ if path:
+ try:
+ return next(self._gitcmd(directory, '--no-optional-locks', 'status', '--porcelain', '--ignored', '--', path))[:2]
+ except StopIteration:
+ return None
+ else:
+ wt_column = ' '
+ index_column = ' '
+ untracked_column = ' '
+ for line in self._gitcmd(directory, '--no-optional-locks', 'status', '--porcelain'):
+ if line[0] == '?':
+ untracked_column = 'U'
+ continue
+ elif line[0] == '!':
+ continue
+
+ if line[0] != ' ':
+ index_column = 'I'
+
+ if line[1] != ' ':
+ wt_column = 'D'
+
+ r = wt_column + index_column + untracked_column
+ return r if r != ' ' else None
diff --git a/powerline/lib/vcs/mercurial.py b/powerline/lib/vcs/mercurial.py
new file mode 100644
index 0000000..09b6e0b
--- /dev/null
+++ b/powerline/lib/vcs/mercurial.py
@@ -0,0 +1,88 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+import hglib
+
+from powerline.lib.vcs import get_branch_name, get_file_status
+from powerline.lib.path import join
+from powerline.lib.encoding import get_preferred_file_contents_encoding
+
+
+def branch_name_from_config_file(directory, config_file):
+ try:
+ with open(config_file, 'rb') as f:
+ raw = f.read()
+ return raw.decode(get_preferred_file_contents_encoding(), 'replace').strip()
+ except Exception:
+ return 'default'
+
+
+class Repository(object):
+ __slots__ = ('directory', 'create_watcher')
+
+ # hg status -> (powerline file status, repo status flag)
+ statuses = {
+ b'M': ('M', 1), b'A': ('A', 1), b'R': ('R', 1), b'!': ('D', 1),
+ b'?': ('U', 2), b'I': ('I', 0), b'C': ('', 0),
+ }
+ repo_statuses_str = (None, 'D ', ' U', 'DU')
+
+ def __init__(self, directory, create_watcher):
+ self.directory = os.path.abspath(directory)
+ self.create_watcher = create_watcher
+
+ def _repo(self, directory):
+ # Cannot create this object once and use always: when repository updates
+ # functions emit invalid results
+ return hglib.open(directory)
+
+ def status(self, path=None):
+ '''Return status of repository or file.
+
+ Without file argument: returns status of the repository:
+
+ :'D?': dirty (tracked modified files: added, removed, deleted, modified),
+ :'?U': untracked-dirty (added, but not tracked files)
+ :None: clean (status is empty)
+
+ With file argument: returns status of this file: `M`odified, `A`dded,
+ `R`emoved, `D`eleted (removed from filesystem, but still tracked),
+ `U`nknown, `I`gnored, (None)Clean.
+ '''
+ if path:
+ return get_file_status(
+ directory=self.directory,
+ dirstate_file=join(self.directory, '.hg', 'dirstate'),
+ file_path=path,
+ ignore_file_name='.hgignore',
+ get_func=self.do_status,
+ create_watcher=self.create_watcher,
+ )
+ return self.do_status(self.directory, path)
+
+ def do_status(self, directory, path):
+ with self._repo(directory) as repo:
+ if path:
+ path = os.path.join(directory, path)
+ statuses = repo.status(include=path, all=True)
+ for status, paths in statuses:
+ if paths:
+ return self.statuses[status][0]
+ return None
+ else:
+ resulting_status = 0
+ for status, paths in repo.status(all=True):
+ if paths:
+ resulting_status |= self.statuses[status][1]
+ return self.repo_statuses_str[resulting_status]
+
+ def branch(self):
+ config_file = join(self.directory, '.hg', 'branch')
+ return get_branch_name(
+ directory=self.directory,
+ config_file=config_file,
+ get_func=branch_name_from_config_file,
+ create_watcher=self.create_watcher,
+ )
diff --git a/powerline/lib/watcher/__init__.py b/powerline/lib/watcher/__init__.py
new file mode 100644
index 0000000..4fe9896
--- /dev/null
+++ b/powerline/lib/watcher/__init__.py
@@ -0,0 +1,76 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+
+from powerline.lib.watcher.stat import StatFileWatcher
+from powerline.lib.watcher.inotify import INotifyFileWatcher
+from powerline.lib.watcher.tree import TreeWatcher
+from powerline.lib.watcher.uv import UvFileWatcher, UvNotFound
+from powerline.lib.inotify import INotifyError
+
+
+def create_file_watcher(pl, watcher_type='auto', expire_time=10):
+ '''Create an object that can watch for changes to specified files
+
+ Use ``.__call__()`` method of the returned object to start watching the file
+ or check whether file has changed since last call.
+
+ Use ``.unwatch()`` method of the returned object to stop watching the file.
+
+ Uses inotify if available, then pyuv, otherwise tracks mtimes. expire_time
+ is the number of minutes after the last query for a given path for the
+ inotify watch for that path to be automatically removed. This conserves
+ kernel resources.
+
+ :param PowerlineLogger pl:
+ Logger.
+ :param str watcher_type
+ One of ``inotify`` (linux only), ``uv``, ``stat``, ``auto``. Determines
+ what watcher will be used. ``auto`` will use ``inotify`` if available,
+ then ``libuv`` and then fall back to ``stat``.
+ :param int expire_time:
+ Number of minutes since last ``.__call__()`` before inotify watcher will
+ stop watching given file.
+ '''
+ if watcher_type == 'stat':
+ pl.debug('Using requested stat-based watcher', prefix='watcher')
+ return StatFileWatcher()
+ if watcher_type == 'inotify':
+ # Explicitly selected inotify watcher: do not catch INotifyError then.
+ pl.debug('Using requested inotify watcher', prefix='watcher')
+ return INotifyFileWatcher(expire_time=expire_time)
+ elif watcher_type == 'uv':
+ pl.debug('Using requested uv watcher', prefix='watcher')
+ return UvFileWatcher()
+
+ if sys.platform.startswith('linux'):
+ try:
+ pl.debug('Trying to use inotify watcher', prefix='watcher')
+ return INotifyFileWatcher(expire_time=expire_time)
+ except INotifyError:
+ pl.info('Failed to create inotify watcher', prefix='watcher')
+
+ try:
+ pl.debug('Using libuv-based watcher')
+ return UvFileWatcher()
+ except UvNotFound:
+ pl.debug('Failed to import pyuv')
+
+ pl.debug('Using stat-based watcher')
+ return StatFileWatcher()
+
+
+def create_tree_watcher(pl, watcher_type='auto', expire_time=10):
+ '''Create an object that can watch for changes in specified directories
+
+ :param PowerlineLogger pl:
+ Logger.
+ :param str watcher_type:
+ Watcher type. Currently the only supported types are ``inotify`` (linux
+ only), ``uv``, ``dummy`` and ``auto``.
+ :param int expire_time:
+ Number of minutes since last ``.__call__()`` before inotify watcher will
+ stop watching given file.
+ '''
+ return TreeWatcher(pl, watcher_type, expire_time)
diff --git a/powerline/lib/watcher/inotify.py b/powerline/lib/watcher/inotify.py
new file mode 100644
index 0000000..c4f1200
--- /dev/null
+++ b/powerline/lib/watcher/inotify.py
@@ -0,0 +1,268 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import errno
+import os
+import ctypes
+
+from threading import RLock
+
+from powerline.lib.inotify import INotify
+from powerline.lib.monotonic import monotonic
+from powerline.lib.path import realpath
+
+
+class INotifyFileWatcher(INotify):
+ def __init__(self, expire_time=10):
+ super(INotifyFileWatcher, self).__init__()
+ self.watches = {}
+ self.modified = {}
+ self.last_query = {}
+ self.lock = RLock()
+ self.expire_time = expire_time * 60
+
+ def expire_watches(self):
+ now = monotonic()
+ for path, last_query in tuple(self.last_query.items()):
+ if last_query - now > self.expire_time:
+ self.unwatch(path)
+
+ def process_event(self, wd, mask, cookie, name):
+ if wd == -1 and (mask & self.Q_OVERFLOW):
+ # We missed some INOTIFY events, so we don't
+ # know the state of any tracked files.
+ for path in tuple(self.modified):
+ if os.path.exists(path):
+ self.modified[path] = True
+ else:
+ self.watches.pop(path, None)
+ self.modified.pop(path, None)
+ self.last_query.pop(path, None)
+ return
+
+ for path, num in tuple(self.watches.items()):
+ if num == wd:
+ if mask & self.IGNORED:
+ self.watches.pop(path, None)
+ self.modified.pop(path, None)
+ self.last_query.pop(path, None)
+ else:
+ if mask & self.ATTRIB:
+ # The watched file could have had its inode changed, in
+ # which case we will not get any more events for this
+ # file, so re-register the watch. For example by some
+ # other file being renamed as this file.
+ try:
+ self.unwatch(path)
+ except OSError:
+ pass
+ try:
+ self.watch(path)
+ except OSError as e:
+ if getattr(e, 'errno', None) != errno.ENOENT:
+ raise
+ else:
+ self.modified[path] = True
+ else:
+ self.modified[path] = True
+
+ def unwatch(self, path):
+ ''' Remove the watch for path. Raises an OSError if removing the watch
+ fails for some reason. '''
+ path = realpath(path)
+ with self.lock:
+ self.modified.pop(path, None)
+ self.last_query.pop(path, None)
+ wd = self.watches.pop(path, None)
+ if wd is not None:
+ if self._rm_watch(self._inotify_fd, wd) != 0:
+ self.handle_error()
+
+ def watch(self, path):
+ ''' Register a watch for the file/directory named path. Raises an OSError if path
+ does not exist. '''
+ path = realpath(path)
+ with self.lock:
+ if path not in self.watches:
+ bpath = path if isinstance(path, bytes) else path.encode(self.fenc)
+ flags = self.MOVE_SELF | self.DELETE_SELF
+ buf = ctypes.c_char_p(bpath)
+ # Try watching path as a directory
+ wd = self._add_watch(self._inotify_fd, buf, flags | self.ONLYDIR)
+ if wd == -1:
+ eno = ctypes.get_errno()
+ if eno != errno.ENOTDIR:
+ self.handle_error()
+ # Try watching path as a file
+ flags |= (self.MODIFY | self.ATTRIB)
+ wd = self._add_watch(self._inotify_fd, buf, flags)
+ if wd == -1:
+ self.handle_error()
+ self.watches[path] = wd
+ self.modified[path] = False
+
+ def is_watching(self, path):
+ with self.lock:
+ return realpath(path) in self.watches
+
+ def __call__(self, path):
+ ''' Return True if path has been modified since the last call. Can
+ raise OSError if the path does not exist. '''
+ path = realpath(path)
+ with self.lock:
+ self.last_query[path] = monotonic()
+ self.expire_watches()
+ if path not in self.watches:
+ # Try to re-add the watch, it will fail if the file does not
+ # exist/you don't have permission
+ self.watch(path)
+ return True
+ self.read(get_name=False)
+ if path not in self.modified:
+ # An ignored event was received which means the path has been
+ # automatically unwatched
+ return True
+ ans = self.modified[path]
+ if ans:
+ self.modified[path] = False
+ return ans
+
+ def close(self):
+ with self.lock:
+ for path in tuple(self.watches):
+ try:
+ self.unwatch(path)
+ except OSError:
+ pass
+ super(INotifyFileWatcher, self).close()
+
+
+class NoSuchDir(ValueError):
+ pass
+
+
+class BaseDirChanged(ValueError):
+ pass
+
+
+class DirTooLarge(ValueError):
+ def __init__(self, bdir):
+ ValueError.__init__(self, 'The directory {0} is too large to monitor. Try increasing the value in /proc/sys/fs/inotify/max_user_watches'.format(bdir))
+
+
+class INotifyTreeWatcher(INotify):
+ is_dummy = False
+
+ def __init__(self, basedir, ignore_event=None):
+ super(INotifyTreeWatcher, self).__init__()
+ self.basedir = realpath(basedir)
+ self.watch_tree()
+ self.modified = True
+ self.ignore_event = (lambda path, name: False) if ignore_event is None else ignore_event
+
+ def watch_tree(self):
+ self.watched_dirs = {}
+ self.watched_rmap = {}
+ try:
+ self.add_watches(self.basedir)
+ except OSError as e:
+ if e.errno == errno.ENOSPC:
+ raise DirTooLarge(self.basedir)
+
+ def add_watches(self, base, top_level=True):
+ ''' Add watches for this directory and all its descendant directories,
+ recursively. '''
+ base = realpath(base)
+ # There may exist a link which leads to an endless
+ # add_watches loop or to maximum recursion depth exceeded
+ if not top_level and base in self.watched_dirs:
+ return
+ try:
+ is_dir = self.add_watch(base)
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ # The entry could have been deleted between listdir() and
+ # add_watch().
+ if top_level:
+ raise NoSuchDir('The dir {0} does not exist'.format(base))
+ return
+ if e.errno == errno.EACCES:
+ # We silently ignore entries for which we don't have permission,
+ # unless they are the top level dir
+ if top_level:
+ raise NoSuchDir('You do not have permission to monitor {0}'.format(base))
+ return
+ raise
+ else:
+ if is_dir:
+ try:
+ files = os.listdir(base)
+ except OSError as e:
+ if e.errno in (errno.ENOTDIR, errno.ENOENT):
+ # The dir was deleted/replaced between the add_watch()
+ # and listdir()
+ if top_level:
+ raise NoSuchDir('The dir {0} does not exist'.format(base))
+ return
+ raise
+ for x in files:
+ self.add_watches(os.path.join(base, x), top_level=False)
+ elif top_level:
+ # The top level dir is a file, not good.
+ raise NoSuchDir('The dir {0} does not exist'.format(base))
+
+ def add_watch(self, path):
+ bpath = path if isinstance(path, bytes) else path.encode(self.fenc)
+ wd = self._add_watch(
+ self._inotify_fd,
+ ctypes.c_char_p(bpath),
+
+ # Ignore symlinks and watch only directories
+ self.DONT_FOLLOW | self.ONLYDIR |
+
+ self.MODIFY | self.CREATE | self.DELETE |
+ self.MOVE_SELF | self.MOVED_FROM | self.MOVED_TO |
+ self.ATTRIB | self.DELETE_SELF
+ )
+ if wd == -1:
+ eno = ctypes.get_errno()
+ if eno == errno.ENOTDIR:
+ return False
+ raise OSError(eno, 'Failed to add watch for: {0}: {1}'.format(path, self.os.strerror(eno)))
+ self.watched_dirs[path] = wd
+ self.watched_rmap[wd] = path
+ return True
+
+ def process_event(self, wd, mask, cookie, name):
+ if wd == -1 and (mask & self.Q_OVERFLOW):
+ # We missed some INOTIFY events, so we don't
+ # know the state of any tracked dirs.
+ self.watch_tree()
+ self.modified = True
+ return
+ path = self.watched_rmap.get(wd, None)
+ if path is not None:
+ if not self.ignore_event(path, name):
+ self.modified = True
+ if mask & self.CREATE:
+ # A new sub-directory might have been created, monitor it.
+ try:
+ if not isinstance(path, bytes):
+ name = name.decode(self.fenc)
+ self.add_watch(os.path.join(path, name))
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ # Deleted before add_watch()
+ pass
+ elif e.errno == errno.ENOSPC:
+ raise DirTooLarge(self.basedir)
+ else:
+ raise
+ if (mask & self.DELETE_SELF or mask & self.MOVE_SELF) and path == self.basedir:
+ raise BaseDirChanged('The directory %s was moved/deleted' % path)
+
+ def __call__(self):
+ self.read()
+ ret = self.modified
+ self.modified = False
+ return ret
diff --git a/powerline/lib/watcher/stat.py b/powerline/lib/watcher/stat.py
new file mode 100644
index 0000000..0c08971
--- /dev/null
+++ b/powerline/lib/watcher/stat.py
@@ -0,0 +1,44 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from threading import RLock
+
+from powerline.lib.path import realpath
+
+
+class StatFileWatcher(object):
+ def __init__(self):
+ self.watches = {}
+ self.lock = RLock()
+
+ def watch(self, path):
+ path = realpath(path)
+ with self.lock:
+ self.watches[path] = os.path.getmtime(path)
+
+ def unwatch(self, path):
+ path = realpath(path)
+ with self.lock:
+ self.watches.pop(path, None)
+
+ def is_watching(self, path):
+ with self.lock:
+ return realpath(path) in self.watches
+
+ def __call__(self, path):
+ path = realpath(path)
+ with self.lock:
+ if path not in self.watches:
+ self.watches[path] = os.path.getmtime(path)
+ return True
+ mtime = os.path.getmtime(path)
+ if mtime != self.watches[path]:
+ self.watches[path] = mtime
+ return True
+ return False
+
+ def close(self):
+ with self.lock:
+ self.watches.clear()
diff --git a/powerline/lib/watcher/tree.py b/powerline/lib/watcher/tree.py
new file mode 100644
index 0000000..7d2b83f
--- /dev/null
+++ b/powerline/lib/watcher/tree.py
@@ -0,0 +1,90 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+
+from powerline.lib.monotonic import monotonic
+from powerline.lib.inotify import INotifyError
+from powerline.lib.path import realpath
+from powerline.lib.watcher.inotify import INotifyTreeWatcher, DirTooLarge, NoSuchDir, BaseDirChanged
+from powerline.lib.watcher.uv import UvTreeWatcher, UvNotFound
+
+
+class DummyTreeWatcher(object):
+ is_dummy = True
+
+ def __init__(self, basedir):
+ self.basedir = realpath(basedir)
+
+ def __call__(self):
+ return False
+
+
+class TreeWatcher(object):
+ def __init__(self, pl, watcher_type, expire_time):
+ self.watches = {}
+ self.last_query_times = {}
+ self.expire_time = expire_time * 60
+ self.pl = pl
+ self.watcher_type = watcher_type
+
+ def get_watcher(self, path, ignore_event):
+ if self.watcher_type == 'inotify':
+ return INotifyTreeWatcher(path, ignore_event=ignore_event)
+ if self.watcher_type == 'uv':
+ return UvTreeWatcher(path, ignore_event=ignore_event)
+ if self.watcher_type == 'dummy':
+ return DummyTreeWatcher(path)
+ # FIXME
+ if self.watcher_type == 'stat':
+ return DummyTreeWatcher(path)
+ if self.watcher_type == 'auto':
+ if sys.platform.startswith('linux'):
+ try:
+ return INotifyTreeWatcher(path, ignore_event=ignore_event)
+ except (INotifyError, DirTooLarge) as e:
+ if not isinstance(e, INotifyError):
+ self.pl.warn('Failed to watch path: {0} with error: {1}'.format(path, e))
+ try:
+ return UvTreeWatcher(path, ignore_event=ignore_event)
+ except UvNotFound:
+ pass
+ return DummyTreeWatcher(path)
+ else:
+ raise ValueError('Unknown watcher type: {0}'.format(self.watcher_type))
+
+ def watch(self, path, ignore_event=None):
+ path = realpath(path)
+ w = self.get_watcher(path, ignore_event)
+ self.watches[path] = w
+ return w
+
+ def expire_old_queries(self):
+ pop = []
+ now = monotonic()
+ for path, lt in self.last_query_times.items():
+ if now - lt > self.expire_time:
+ pop.append(path)
+ for path in pop:
+ del self.last_query_times[path]
+
+ def __call__(self, path, ignore_event=None):
+ path = realpath(path)
+ self.expire_old_queries()
+ self.last_query_times[path] = monotonic()
+ w = self.watches.get(path, None)
+ if w is None:
+ try:
+ self.watch(path, ignore_event=ignore_event)
+ except NoSuchDir:
+ pass
+ return True
+ try:
+ return w()
+ except BaseDirChanged:
+ self.watches.pop(path, None)
+ return True
+ except DirTooLarge as e:
+ self.pl.warn(str(e))
+ self.watches[path] = DummyTreeWatcher(path)
+ return False
diff --git a/powerline/lib/watcher/uv.py b/powerline/lib/watcher/uv.py
new file mode 100644
index 0000000..272db0f
--- /dev/null
+++ b/powerline/lib/watcher/uv.py
@@ -0,0 +1,207 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from collections import defaultdict
+from threading import RLock
+from functools import partial
+from threading import Thread
+from errno import ENOENT
+
+from powerline.lib.path import realpath
+from powerline.lib.encoding import get_preferred_file_name_encoding
+
+
+class UvNotFound(NotImplementedError):
+ pass
+
+
+pyuv = None
+pyuv_version_info = None
+
+
+def import_pyuv():
+ global pyuv
+ global pyuv_version_info
+ if not pyuv:
+ try:
+ pyuv = __import__('pyuv')
+ except ImportError:
+ raise UvNotFound
+ else:
+ pyuv_version_info = tuple((int(c) for c in pyuv.__version__.split('.')))
+
+
+class UvThread(Thread):
+ daemon = True
+
+ def __init__(self, loop):
+ self.uv_loop = loop
+ self.async_handle = pyuv.Async(loop, self._async_cb)
+ super(UvThread, self).__init__()
+
+ def _async_cb(self, handle):
+ self.uv_loop.stop()
+ self.async_handle.close()
+
+ def run(self):
+ self.uv_loop.run()
+
+ def join(self):
+ self.async_handle.send()
+ return super(UvThread, self).join()
+
+
+_uv_thread = None
+
+
+def start_uv_thread():
+ global _uv_thread
+ if _uv_thread is None:
+ loop = pyuv.Loop()
+ _uv_thread = UvThread(loop)
+ _uv_thread.start()
+ return _uv_thread.uv_loop
+
+
+def normpath(path, fenc):
+ path = realpath(path)
+ if isinstance(path, bytes):
+ return path.decode(fenc)
+ else:
+ return path
+
+
+class UvWatcher(object):
+ def __init__(self):
+ import_pyuv()
+ self.watches = {}
+ self.lock = RLock()
+ self.loop = start_uv_thread()
+ self.fenc = get_preferred_file_name_encoding()
+ if pyuv_version_info >= (1, 0):
+ self._start_watch = self._start_watch_1_x
+ else:
+ self._start_watch = self._start_watch_0_x
+
+ def _start_watch_1_x(self, path):
+ handle = pyuv.fs.FSEvent(self.loop)
+ handle.start(path, 0, partial(self._record_event, path))
+ self.watches[path] = handle
+
+ def _start_watch_0_x(self, path):
+ self.watches[path] = pyuv.fs.FSEvent(
+ self.loop,
+ path,
+ partial(self._record_event, path),
+ pyuv.fs.UV_CHANGE | pyuv.fs.UV_RENAME
+ )
+
+ def watch(self, path):
+ path = normpath(path, self.fenc)
+ with self.lock:
+ if path not in self.watches:
+ try:
+ self._start_watch(path)
+ except pyuv.error.FSEventError as e:
+ code = e.args[0]
+ if code == pyuv.errno.UV_ENOENT:
+ raise OSError(ENOENT, 'No such file or directory: ' + path)
+ else:
+ raise
+
+ def unwatch(self, path):
+ path = normpath(path, self.fenc)
+ with self.lock:
+ try:
+ watch = self.watches.pop(path)
+ except KeyError:
+ return
+ watch.close(partial(self._stopped_watching, path))
+
+ def is_watching(self, path):
+ with self.lock:
+ return normpath(path, self.fenc) in self.watches
+
+ def __del__(self):
+ try:
+ lock = self.lock
+ except AttributeError:
+ pass
+ else:
+ with lock:
+ while self.watches:
+ path, watch = self.watches.popitem()
+ watch.close(partial(self._stopped_watching, path))
+
+
+class UvFileWatcher(UvWatcher):
+ def __init__(self):
+ super(UvFileWatcher, self).__init__()
+ self.events = defaultdict(list)
+
+ def _record_event(self, path, fsevent_handle, filename, events, error):
+ with self.lock:
+ self.events[path].append(events)
+ if events | pyuv.fs.UV_RENAME:
+ if not os.path.exists(path):
+ self.watches.pop(path).close()
+
+ def _stopped_watching(self, path, *args):
+ self.events.pop(path, None)
+
+ def __call__(self, path):
+ path = normpath(path, self.fenc)
+ with self.lock:
+ events = self.events.pop(path, None)
+ if events:
+ return True
+ if path not in self.watches:
+ self.watch(path)
+ return True
+ return False
+
+
+class UvTreeWatcher(UvWatcher):
+ is_dummy = False
+
+ def __init__(self, basedir, ignore_event=None):
+ super(UvTreeWatcher, self).__init__()
+ self.ignore_event = ignore_event or (lambda path, name: False)
+ self.basedir = normpath(basedir, self.fenc)
+ self.modified = True
+ self.watch_directory(self.basedir)
+
+ def watch_directory(self, path):
+ for root, dirs, files in os.walk(normpath(path, self.fenc)):
+ self.watch_one_directory(root)
+
+ def watch_one_directory(self, dirname):
+ try:
+ self.watch(dirname)
+ except OSError:
+ pass
+
+ def _stopped_watching(self, path, *args):
+ pass
+
+ def _record_event(self, path, fsevent_handle, filename, events, error):
+ if not self.ignore_event(path, filename):
+ self.modified = True
+ if events == pyuv.fs.UV_CHANGE | pyuv.fs.UV_RENAME:
+ # Stat changes to watched directory are UV_CHANGE|UV_RENAME. It
+ # is weird.
+ pass
+ elif events | pyuv.fs.UV_RENAME:
+ if not os.path.isdir(path):
+ self.unwatch(path)
+ else:
+ full_name = os.path.join(path, filename)
+ if os.path.isdir(full_name):
+ # For some reason mkdir and rmdir both fall into this
+ # category
+ self.watch_directory(full_name)
+
+ def __call__(self):
+ return self.__dict__.pop('modified', False)
diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py
new file mode 100644
index 0000000..8c68271
--- /dev/null
+++ b/powerline/lint/__init__.py
@@ -0,0 +1,625 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import logging
+
+from collections import defaultdict
+from itertools import chain
+from functools import partial
+
+from powerline import generate_config_finder, get_config_paths, load_config
+from powerline.segments.vim import vim_modes
+from powerline.lib.dict import mergedicts_copy
+from powerline.lib.config import ConfigLoader
+from powerline.lib.unicode import unicode
+from powerline.lib.path import join
+from powerline.lint.markedjson import load
+from powerline.lint.markedjson.error import echoerr, EchoErr, MarkedError
+from powerline.lint.checks import (check_matcher_func, check_ext, check_config, check_top_theme,
+ check_color, check_translated_group_name, check_group,
+ check_segment_module, check_exinclude_function, type_keys,
+ check_segment_function, check_args, get_one_segment_function,
+ check_highlight_groups, check_highlight_group, check_full_segment_data,
+ get_all_possible_functions, check_segment_data_key, register_common_name,
+ highlight_group_spec, check_log_file_level, check_logging_handler)
+from powerline.lint.spec import Spec
+from powerline.lint.context import Context
+
+
+def open_file(path):
+ return open(path, 'rb')
+
+
+def generate_json_config_loader(lhadproblem):
+ def load_json_config(config_file_path, load=load, open_file=open_file):
+ with open_file(config_file_path) as config_file_fp:
+ r, hadproblem = load(config_file_fp)
+ if hadproblem:
+ lhadproblem[0] = True
+ return r
+ return load_json_config
+
+
+function_name_re = '^(\w+\.)*[a-zA-Z_]\w*$'
+
+
+divider_spec = Spec().printable().len(
+ 'le', 3, (lambda value: 'Divider {0!r} is too large!'.format(value))).copy
+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
+gen_components_spec = (lambda *components: Spec().list(Spec().type(unicode).oneof(set(components))))
+log_level_spec = Spec().re('^[A-Z]+$').func(
+ (lambda value, *args: (True, True, not hasattr(logging, value))),
+ (lambda value: 'unknown debugging level {0}'.format(value))
+).copy
+log_format_spec = Spec().type(unicode).copy
+main_spec = (Spec(
+ common=Spec(
+ default_top_theme=top_theme_spec().optional(),
+ term_truecolor=Spec().type(bool).optional(),
+ term_escape_style=Spec().type(unicode).oneof(set(('auto', 'xterm', 'fbterm'))).optional(),
+ # Python is capable of loading from zip archives. Thus checking path
+ # only for existence of the path, not for it being a directory
+ paths=Spec().list(
+ (lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))),
+ (lambda value: 'path does not exist: {0}'.format(value))
+ ).optional(),
+ log_file=Spec().either(
+ Spec().type(unicode).func(
+ (
+ lambda value, *args: (
+ True,
+ True,
+ not os.path.isdir(os.path.dirname(os.path.expanduser(value)))
+ )
+ ),
+ (lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value)))
+ ),
+ Spec().list(Spec().either(
+ Spec().type(unicode, type(None)),
+ Spec().tuple(
+ Spec().re(function_name_re).func(check_logging_handler),
+ Spec().tuple(
+ Spec().type(list).optional(),
+ Spec().type(dict).optional(),
+ ),
+ log_level_spec().func(check_log_file_level).optional(),
+ log_format_spec().optional(),
+ ),
+ ))
+ ).optional(),
+ log_level=log_level_spec().optional(),
+ log_format=log_format_spec().optional(),
+ interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(),
+ reload_config=Spec().type(bool).optional(),
+ watcher=Spec().type(unicode).oneof(set(('auto', 'inotify', 'stat'))).optional(),
+ ).context_message('Error while loading common configuration (key {key})'),
+ ext=Spec(
+ vim=ext_spec().update(
+ components=gen_components_spec('statusline', 'tabline').optional(),
+ local_themes=Spec(
+ __tabline__=ext_theme_spec(),
+ ).unknown_spec(
+ Spec().re(function_name_re).func(partial(check_matcher_func, 'vim')),
+ ext_theme_spec()
+ ),
+ ).optional(),
+ ipython=ext_spec().update(
+ local_themes=Spec(
+ in2=ext_theme_spec(),
+ out=ext_theme_spec(),
+ rewrite=ext_theme_spec(),
+ ),
+ ).optional(),
+ shell=ext_spec().update(
+ components=gen_components_spec('tmux', 'prompt').optional(),
+ local_themes=Spec(
+ continuation=ext_theme_spec(),
+ select=ext_theme_spec(),
+ ),
+ ).optional(),
+ wm=ext_spec().update(
+ local_themes=Spec().unknown_spec(
+ Spec().re('^[0-9A-Za-z-]+$'),
+ ext_theme_spec()
+ ).optional(),
+ update_interval=Spec().cmp('gt', 0.0).optional(),
+ ).optional(),
+ ).unknown_spec(
+ check_ext,
+ ext_spec(),
+ ).context_message('Error while loading extensions configuration (key {key})'),
+).context_message('Error while loading main configuration'))
+
+term_color_spec = Spec().unsigned().cmp('le', 255).copy
+true_color_spec = Spec().re(
+ '^[0-9a-fA-F]{6}$',
+ (lambda value: '"{0}" is not a six-digit hexadecimal unsigned integer written as a string'.format(value))
+).copy
+colors_spec = (Spec(
+ colors=Spec().unknown_spec(
+ Spec().ident(),
+ Spec().either(
+ Spec().tuple(term_color_spec(), true_color_spec()),
+ term_color_spec()
+ )
+ ).context_message('Error while checking colors (key {key})'),
+ gradients=Spec().unknown_spec(
+ Spec().ident(),
+ Spec().tuple(
+ Spec().len('gt', 1).list(term_color_spec()),
+ Spec().len('gt', 1).list(true_color_spec()).optional(),
+ )
+ ).context_message('Error while checking gradients (key {key})'),
+).context_message('Error while loading colors configuration'))
+
+
+color_spec = Spec().type(unicode).func(check_color).copy
+name_spec = Spec().type(unicode).len('gt', 0).optional().copy
+group_name_spec = Spec().ident().copy
+group_spec = Spec().either(Spec(
+ fg=color_spec(),
+ bg=color_spec(),
+ attrs=Spec().list(Spec().type(unicode).oneof(set(('bold', 'italic', 'underline')))),
+), group_name_spec().func(check_group)).copy
+groups_spec = Spec().unknown_spec(
+ group_name_spec(),
+ group_spec(),
+).context_message('Error while loading groups (key {key})').copy
+colorscheme_spec = (Spec(
+ name=name_spec(),
+ groups=groups_spec(),
+).context_message('Error while loading coloscheme'))
+mode_translations_value_spec = Spec(
+ colors=Spec().unknown_spec(
+ color_spec(),
+ color_spec(),
+ ).optional(),
+ groups=Spec().unknown_spec(
+ group_name_spec().func(check_translated_group_name),
+ group_spec(),
+ ).optional(),
+).copy
+top_colorscheme_spec = (Spec(
+ name=name_spec(),
+ groups=groups_spec(),
+ mode_translations=Spec().unknown_spec(
+ Spec().type(unicode),
+ mode_translations_value_spec(),
+ ).optional().context_message('Error while loading mode translations (key {key})').optional(),
+).context_message('Error while loading top-level coloscheme'))
+vim_mode_spec = Spec().oneof(set(list(vim_modes) + ['nc', 'tab_nc', 'buf_nc'])).copy
+vim_colorscheme_spec = (Spec(
+ name=name_spec(),
+ groups=groups_spec(),
+ mode_translations=Spec().unknown_spec(
+ vim_mode_spec(),
+ mode_translations_value_spec(),
+ ).optional().context_message('Error while loading mode translations (key {key})'),
+).context_message('Error while loading vim colorscheme'))
+shell_mode_spec = Spec().re('^(?:[\w\-]+|\.safe)$').copy
+shell_colorscheme_spec = (Spec(
+ name=name_spec(),
+ groups=groups_spec(),
+ mode_translations=Spec().unknown_spec(
+ shell_mode_spec(),
+ mode_translations_value_spec(),
+ ).optional().context_message('Error while loading mode translations (key {key})'),
+).context_message('Error while loading shell colorscheme'))
+
+
+args_spec = Spec(
+ pl=Spec().error('pl object must be set by powerline').optional(),
+ segment_info=Spec().error('Segment info dictionary must be set by powerline').optional(),
+).unknown_spec(Spec(), Spec()).optional().copy
+segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy
+exinclude_spec = Spec().re(function_name_re).func(check_exinclude_function).copy
+segment_spec_base = Spec(
+ name=Spec().re('^[a-zA-Z_]\w*$').optional(),
+ function=Spec().re(function_name_re).func(check_segment_function).optional(),
+ exclude_modes=Spec().list(vim_mode_spec()).optional(),
+ include_modes=Spec().list(vim_mode_spec()).optional(),
+ exclude_function=exinclude_spec().optional(),
+ include_function=exinclude_spec().optional(),
+ draw_hard_divider=Spec().type(bool).optional(),
+ draw_soft_divider=Spec().type(bool).optional(),
+ draw_inner_divider=Spec().type(bool).optional(),
+ display=Spec().type(bool).optional(),
+ module=segment_module_spec(),
+ priority=Spec().type(int, float, type(None)).optional(),
+ after=Spec().printable().optional(),
+ before=Spec().printable().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_function, *args, **kwargs)),
+ contents=Spec().printable().optional(),
+ highlight_groups=Spec().list(
+ highlight_group_spec().re(
+ '^(?:(?!:divider$).)+$',
+ (lambda value: 'it is recommended that only divider highlight group names end with ":divider"')
+ )
+ ).func(check_highlight_groups).optional(),
+ divider_highlight_group=highlight_group_spec().func(check_highlight_group).re(
+ ':divider$',
+ (lambda value: 'it is recommended that divider highlight group names end with ":divider"')
+ ).optional(),
+).func(check_full_segment_data).copy
+subsegment_spec = segment_spec_base().update(
+ type=Spec().oneof(set((key for key in type_keys if key != 'segment_list'))).optional(),
+)
+segment_spec = segment_spec_base().update(
+ type=Spec().oneof(type_keys).optional(),
+ segments=Spec().optional().list(subsegment_spec),
+)
+segments_spec = Spec().optional().list(segment_spec).copy
+segdict_spec = Spec(
+ left=segments_spec().context_message('Error while loading segments from left side (key {key})'),
+ right=segments_spec().context_message('Error while loading segments from right side (key {key})'),
+).func(
+ (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
+divside_spec = Spec(
+ hard=divider_spec(),
+ soft=divider_spec(),
+).copy
+segment_data_value_spec = Spec(
+ after=Spec().printable().optional(),
+ before=Spec().printable().optional(),
+ display=Spec().type(bool).optional(),
+ args=args_spec().func(lambda *args, **kwargs: check_args(get_all_possible_functions, *args, **kwargs)),
+ contents=Spec().printable().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(),
+ cursor_space=Spec().type(int, float).cmp('le', 100).cmp('gt', 0).optional(),
+ cursor_columns=Spec().type(int).cmp('gt', 0).optional(),
+).context_message('Error while loading theme').copy
+top_theme_spec = common_theme_spec().update(
+ dividers=dividers_spec(),
+ spaces=spaces_spec(),
+ use_non_breaking_spaces=Spec().type(bool).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})'),
+)
+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()),
+)
+
+
+def register_common_names():
+ register_common_name('player', 'powerline.segments.common.players', '_player')
+
+
+def load_json_file(path):
+ with open_file(path) as F:
+ try:
+ config, hadproblem = load(F)
+ except MarkedError as e:
+ return True, None, str(e)
+ else:
+ return hadproblem, config, None
+
+
+def updated_with_config(d):
+ hadproblem, config, error = load_json_file(d['path'])
+ d.update(
+ hadproblem=hadproblem,
+ config=config,
+ error=error,
+ )
+ return d
+
+
+def find_all_ext_config_files(search_paths, subdir):
+ for config_root in search_paths:
+ top_config_subpath = join(config_root, subdir)
+ if not os.path.isdir(top_config_subpath):
+ if os.path.exists(top_config_subpath):
+ yield {
+ 'error': 'Path {0} is not a directory'.format(top_config_subpath),
+ 'path': top_config_subpath,
+ }
+ continue
+ for ext_name in os.listdir(top_config_subpath):
+ ext_path = os.path.join(top_config_subpath, ext_name)
+ if not os.path.isdir(ext_path):
+ if ext_name.endswith('.json') and os.path.isfile(ext_path):
+ yield updated_with_config({
+ 'error': False,
+ 'path': ext_path,
+ 'name': ext_name[:-5],
+ 'ext': None,
+ 'type': 'top_' + subdir,
+ })
+ else:
+ yield {
+ 'error': 'Path {0} is not a directory or configuration file'.format(ext_path),
+ 'path': ext_path,
+ }
+ continue
+ for config_file_name in os.listdir(ext_path):
+ config_file_path = os.path.join(ext_path, config_file_name)
+ if config_file_name.endswith('.json') and os.path.isfile(config_file_path):
+ yield updated_with_config({
+ 'error': False,
+ 'path': config_file_path,
+ 'name': config_file_name[:-5],
+ 'ext': ext_name,
+ 'type': subdir,
+ })
+ else:
+ yield {
+ 'error': 'Path {0} is not a configuration file'.format(config_file_path),
+ 'path': config_file_path,
+ }
+
+
+def dict2(d):
+ return defaultdict(dict, ((k, dict(v)) for k, v in d.items()))
+
+
+def check(paths=None, debug=False, echoerr=echoerr, require_ext=None):
+ '''Check configuration sanity
+
+ :param list paths:
+ Paths from which configuration should be loaded.
+ :param bool debug:
+ Determines whether some information useful for debugging linter should
+ be output.
+ :param function echoerr:
+ Function that will be used to echo the error(s). Should accept four
+ optional keyword parameters: ``problem`` and ``problem_mark``, and
+ ``context`` and ``context_mark``.
+ :param str require_ext:
+ Require configuration for some extension to be present.
+
+ :return:
+ ``False`` if user configuration seems to be completely sane and ``True``
+ if some problems were found.
+ '''
+ hadproblem = False
+
+ register_common_names()
+ search_paths = paths or get_config_paths()
+ find_config_files = generate_config_finder(lambda: search_paths)
+
+ logger = logging.getLogger('powerline-lint')
+ logger.setLevel(logging.DEBUG if debug else logging.ERROR)
+ logger.addHandler(logging.StreamHandler())
+
+ ee = EchoErr(echoerr, logger)
+
+ if require_ext:
+ used_main_spec = main_spec.copy()
+ try:
+ used_main_spec['ext'][require_ext].required()
+ except KeyError:
+ used_main_spec['ext'][require_ext] = ext_spec()
+ else:
+ used_main_spec = main_spec
+
+ lhadproblem = [False]
+ load_json_config = generate_json_config_loader(lhadproblem)
+
+ config_loader = ConfigLoader(run_once=True, load=load_json_config)
+
+ lists = {
+ 'colorschemes': set(),
+ 'themes': set(),
+ 'exts': set(),
+ }
+ found_dir = {
+ 'themes': False,
+ 'colorschemes': False,
+ }
+ config_paths = defaultdict(lambda: defaultdict(dict))
+ loaded_configs = defaultdict(lambda: defaultdict(dict))
+ for d in chain(
+ find_all_ext_config_files(search_paths, 'colorschemes'),
+ find_all_ext_config_files(search_paths, 'themes'),
+ ):
+ if d['error']:
+ hadproblem = True
+ ee(problem=d['error'])
+ continue
+ if d['hadproblem']:
+ hadproblem = True
+ if d['ext']:
+ found_dir[d['type']] = True
+ lists['exts'].add(d['ext'])
+ if d['name'] == '__main__':
+ pass
+ elif d['name'].startswith('__') or d['name'].endswith('__'):
+ hadproblem = True
+ ee(problem='File name is not supposed to start or end with “__”: {0}'.format(
+ d['path']))
+ else:
+ lists[d['type']].add(d['name'])
+ config_paths[d['type']][d['ext']][d['name']] = d['path']
+ loaded_configs[d['type']][d['ext']][d['name']] = d['config']
+ else:
+ config_paths[d['type']][d['name']] = d['path']
+ loaded_configs[d['type']][d['name']] = d['config']
+
+ for typ in ('themes', 'colorschemes'):
+ if not found_dir[typ]:
+ hadproblem = True
+ ee(problem='Subdirectory {0} was not found in paths {1}'.format(typ, ', '.join(search_paths)))
+
+ diff = set(config_paths['colorschemes']) - set(config_paths['themes'])
+ if diff:
+ hadproblem = True
+ for ext in diff:
+ typ = 'colorschemes' if ext in config_paths['themes'] else 'themes'
+ if not config_paths['top_' + typ] or typ == 'themes':
+ ee(problem='{0} extension {1} not present in {2}'.format(
+ ext,
+ 'configuration' if (
+ ext in loaded_configs['themes'] and ext in loaded_configs['colorschemes']
+ ) else 'directory',
+ typ,
+ ))
+
+ try:
+ main_config = load_config('config', find_config_files, config_loader)
+ except IOError:
+ main_config = {}
+ ee(problem='Configuration file not found: config.json')
+ hadproblem = True
+ except MarkedError as e:
+ main_config = {}
+ ee(problem=str(e))
+ hadproblem = True
+ else:
+ if used_main_spec.match(
+ main_config,
+ data={'configs': config_paths, 'lists': lists},
+ context=Context(main_config),
+ echoerr=ee
+ )[1]:
+ hadproblem = True
+
+ import_paths = [os.path.expanduser(path) for path in main_config.get('common', {}).get('paths', [])]
+
+ try:
+ colors_config = load_config('colors', find_config_files, config_loader)
+ except IOError:
+ colors_config = {}
+ ee(problem='Configuration file not found: colors.json')
+ hadproblem = True
+ except MarkedError as e:
+ colors_config = {}
+ ee(problem=str(e))
+ hadproblem = True
+ else:
+ if colors_spec.match(colors_config, context=Context(colors_config), echoerr=ee)[1]:
+ hadproblem = True
+
+ if lhadproblem[0]:
+ hadproblem = True
+
+ top_colorscheme_configs = dict(loaded_configs['top_colorschemes'])
+ data = {
+ 'ext': None,
+ 'top_colorscheme_configs': top_colorscheme_configs,
+ 'ext_colorscheme_configs': {},
+ 'colors_config': colors_config
+ }
+ for colorscheme, config in loaded_configs['top_colorschemes'].items():
+ data['colorscheme'] = colorscheme
+ if top_colorscheme_spec.match(config, context=Context(config), data=data, echoerr=ee)[1]:
+ hadproblem = True
+
+ ext_colorscheme_configs = dict2(loaded_configs['colorschemes'])
+ for ext, econfigs in ext_colorscheme_configs.items():
+ data = {
+ 'ext': ext,
+ 'top_colorscheme_configs': top_colorscheme_configs,
+ 'ext_colorscheme_configs': ext_colorscheme_configs,
+ 'colors_config': colors_config,
+ }
+ for colorscheme, config in econfigs.items():
+ data['colorscheme'] = colorscheme
+ if ext == 'vim':
+ spec = vim_colorscheme_spec
+ elif ext == 'shell':
+ spec = shell_colorscheme_spec
+ else:
+ spec = colorscheme_spec
+ if spec.match(config, context=Context(config), data=data, echoerr=ee)[1]:
+ hadproblem = True
+
+ colorscheme_configs = {}
+ for ext in lists['exts']:
+ colorscheme_configs[ext] = {}
+ for colorscheme in lists['colorschemes']:
+ econfigs = ext_colorscheme_configs[ext]
+ ecconfigs = econfigs.get(colorscheme)
+ mconfigs = (
+ top_colorscheme_configs.get(colorscheme),
+ econfigs.get('__main__'),
+ ecconfigs,
+ )
+ if not (mconfigs[0] or mconfigs[2]):
+ continue
+ config = None
+ for mconfig in mconfigs:
+ if not mconfig:
+ continue
+ if config:
+ config = mergedicts_copy(config, mconfig)
+ else:
+ config = mconfig
+ colorscheme_configs[ext][colorscheme] = config
+
+ theme_configs = dict2(loaded_configs['themes'])
+ top_theme_configs = dict(loaded_configs['top_themes'])
+ 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 == '__main__':
+ data['theme_type'] = 'main'
+ spec = main_theme_spec
+ else:
+ data['theme_type'] = 'regular'
+ spec = theme_spec
+ if spec.match(config, context=Context(config), data=data, echoerr=ee)[1]:
+ hadproblem = True
+
+ for top_theme, config in top_theme_configs.items():
+ data = {
+ 'ext': None,
+ 'colorscheme_configs': colorscheme_configs,
+ 'import_paths': import_paths,
+ 'main_config': main_config,
+ 'theme_configs': theme_configs,
+ 'ext_theme_configs': None,
+ 'colors_config': colors_config
+ }
+ data['theme_type'] = 'top'
+ data['theme'] = top_theme
+ if top_theme_spec.match(config, context=Context(config), data=data, echoerr=ee)[1]:
+ hadproblem = True
+
+ return hadproblem
diff --git a/powerline/lint/checks.py b/powerline/lint/checks.py
new file mode 100644
index 0000000..8d9cb12
--- /dev/null
+++ b/powerline/lint/checks.py
@@ -0,0 +1,866 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import re
+import logging
+
+from collections import defaultdict
+
+from powerline.lib.threaded import ThreadedSegment
+from powerline.lib.unicode import unicode
+from powerline.lint.markedjson.markedvalue import MarkedUnicode
+from powerline.lint.markedjson.error import DelayedEchoErr, Mark
+from powerline.lint.selfcheck import havemarks
+from powerline.lint.context import JStr, list_themes
+from powerline.lint.imp import WithPath, import_function, import_segment
+from powerline.lint.spec import Spec
+from powerline.lint.inspect import getconfigargspec
+
+
+list_sep = JStr(', ')
+
+
+generic_keys = set((
+ 'exclude_modes', 'include_modes',
+ 'exclude_function', 'include_function',
+ 'width', 'align',
+ 'name',
+ 'draw_soft_divider', 'draw_hard_divider',
+ 'priority',
+ 'after', 'before',
+ 'display'
+))
+type_keys = {
+ 'function': set(('function', 'args', 'draw_inner_divider')),
+ 'string': set(('contents', 'type', 'highlight_groups', 'divider_highlight_group')),
+ 'segment_list': set(('function', 'segments', 'args', 'type')),
+}
+required_keys = {
+ 'function': set(('function',)),
+ 'string': set(()),
+ 'segment_list': set(('function', 'segments',)),
+}
+highlight_keys = set(('highlight_groups', 'name'))
+
+
+def get_function_strings(function_name, context, ext):
+ if '.' in function_name:
+ module, function_name = function_name.rpartition('.')[::2]
+ else:
+ module = context[0][1].get(
+ 'default_module', MarkedUnicode('powerline.segments.' + ext, None))
+ return module, function_name
+
+
+def check_matcher_func(ext, match_name, data, context, echoerr):
+ havemarks(match_name)
+ import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])]
+
+ match_module, separator, match_function = match_name.rpartition('.')
+ if not separator:
+ match_module = 'powerline.matchers.{0}'.format(ext)
+ match_function = match_name
+ with WithPath(import_paths):
+ try:
+ func = getattr(__import__(str(match_module), fromlist=[str(match_function)]), str(match_function))
+ except ImportError:
+ echoerr(context='Error while loading matcher functions',
+ problem='failed to load module {0}'.format(match_module),
+ problem_mark=match_name.mark)
+ return True, False, True
+ except AttributeError:
+ echoerr(context='Error while loading matcher functions',
+ problem='failed to load matcher function {0}'.format(match_function),
+ problem_mark=match_name.mark)
+ return True, False, True
+
+ if not callable(func):
+ echoerr(context='Error while loading matcher functions',
+ problem='loaded “function” {0} is not callable'.format(match_function),
+ problem_mark=match_name.mark)
+ return True, False, True
+
+ if hasattr(func, 'func_code') and hasattr(func.func_code, 'co_argcount'):
+ if func.func_code.co_argcount != 1:
+ echoerr(
+ context='Error while loading matcher functions',
+ problem=(
+ 'function {0} accepts {1} arguments instead of 1. '
+ 'Are you sure it is the proper function?'
+ ).format(match_function, func.func_code.co_argcount),
+ problem_mark=match_name.mark
+ )
+
+ return True, False, False
+
+
+def check_ext(ext, data, context, echoerr):
+ havemarks(ext)
+ hadsomedirs = False
+ hadproblem = False
+ if ext not in data['lists']['exts']:
+ hadproblem = True
+ echoerr(context='Error while loading {0} extension configuration'.format(ext),
+ context_mark=ext.mark,
+ problem='extension configuration does not exist')
+ else:
+ for typ in ('themes', 'colorschemes'):
+ if ext not in data['configs'][typ] and not data['configs']['top_' + typ]:
+ hadproblem = True
+ echoerr(context='Error while loading {0} extension configuration'.format(ext),
+ context_mark=ext.mark,
+ problem='{0} configuration does not exist'.format(typ))
+ else:
+ hadsomedirs = True
+ return hadsomedirs, hadproblem
+
+
+def check_config(d, theme, data, context, echoerr):
+ if len(context) == 4:
+ ext = context[-2][0]
+ else:
+ # local_themes
+ ext = context[-3][0]
+ if ext not in data['lists']['exts']:
+ echoerr(context='Error while loading {0} extension configuration'.format(ext),
+ context_mark=ext.mark,
+ problem='extension configuration does not exist')
+ return True, False, True
+ if (
+ (ext not in data['configs'][d] or theme not in data['configs'][d][ext])
+ and theme not in data['configs']['top_' + d]
+ ):
+ echoerr(context='Error while loading {0} from {1} extension configuration'.format(d[:-1], ext),
+ problem='failed to find configuration file {0}/{1}/{2}.json'.format(d, ext, theme),
+ problem_mark=theme.mark)
+ return True, False, True
+ return True, False, False
+
+
+def check_top_theme(theme, data, context, echoerr):
+ havemarks(theme)
+ if theme not in data['configs']['top_themes']:
+ echoerr(context='Error while checking extension configuration (key {key})'.format(key=context.key),
+ 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
+
+
+def check_color(color, data, context, echoerr):
+ havemarks(color)
+ if (color not in data['colors_config'].get('colors', {})
+ and color not in data['colors_config'].get('gradients', {})):
+ echoerr(
+ context='Error while checking highlight group in colorscheme (key {key})'.format(
+ key=context.key),
+ problem='found unexistent color or gradient {0}'.format(color),
+ problem_mark=color.mark
+ )
+ return True, False, True
+ return True, False, False
+
+
+def check_translated_group_name(group, data, context, echoerr):
+ return check_group(group, data, context, echoerr)
+
+
+def check_group(group, data, context, echoerr):
+ havemarks(group)
+ if not isinstance(group, unicode):
+ return True, False, False
+ colorscheme = data['colorscheme']
+ ext = data['ext']
+ configs = None
+ if ext:
+ def listed_key(d, k):
+ try:
+ return [d[k]]
+ except KeyError:
+ return []
+
+ if colorscheme == '__main__':
+ colorscheme_names = set(data['ext_colorscheme_configs'][ext])
+ colorscheme_names.update(data['top_colorscheme_configs'])
+ colorscheme_names.discard('__main__')
+ configs = [
+ (
+ name,
+ listed_key(data['ext_colorscheme_configs'][ext], name)
+ + listed_key(data['ext_colorscheme_configs'][ext], '__main__')
+ + listed_key(data['top_colorscheme_configs'], name)
+ )
+ for name in colorscheme_names
+ ]
+ else:
+ configs = [
+ (
+ colorscheme,
+ listed_key(data['ext_colorscheme_configs'][ext], colorscheme)
+ + listed_key(data['ext_colorscheme_configs'][ext], '__main__')
+ + listed_key(data['top_colorscheme_configs'], colorscheme)
+ )
+ ]
+ else:
+ try:
+ configs = [(colorscheme, [data['top_colorscheme_configs'][colorscheme]])]
+ except KeyError:
+ pass
+ hadproblem = False
+ for new_colorscheme, config_lst in configs:
+ not_found = []
+ new_data = data.copy()
+ new_data['colorscheme'] = new_colorscheme
+ for config in config_lst:
+ havemarks(config)
+ try:
+ group_data = config['groups'][group]
+ except KeyError:
+ not_found.append(config.mark.name)
+ else:
+ proceed, echo, chadproblem = check_group(
+ group_data,
+ new_data,
+ context,
+ echoerr,
+ )
+ if chadproblem:
+ hadproblem = True
+ if not proceed:
+ break
+ if not_found and len(not_found) == len(config_lst):
+ echoerr(
+ context='Error while checking group definition in colorscheme (key {key})'.format(
+ key=context.key),
+ problem='name {0} is not present anywhere in {1} {2} {3} colorschemes: {4}'.format(
+ group, len(not_found), ext, new_colorscheme, ', '.join(not_found)),
+ problem_mark=group.mark
+ )
+ hadproblem = True
+ return True, False, hadproblem
+
+
+def check_key_compatibility(segment, data, context, echoerr):
+ havemarks(segment)
+ segment_type = segment.get('type', MarkedUnicode('function', None))
+ havemarks(segment_type)
+
+ if segment_type not in type_keys:
+ echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
+ problem='found segment with unknown type {0}'.format(segment_type),
+ problem_mark=segment_type.mark)
+ return False, False, True
+
+ hadproblem = False
+
+ keys = set(segment)
+ if not ((keys - generic_keys) < type_keys[segment_type]):
+ unknown_keys = keys - generic_keys - type_keys[segment_type]
+ echoerr(
+ context='Error while checking segments (key {key})'.format(key=context.key),
+ context_mark=context[-1][1].mark,
+ problem='found keys not used with the current segment type: {0}'.format(
+ list_sep.join(unknown_keys)),
+ problem_mark=list(unknown_keys)[0].mark
+ )
+ hadproblem = True
+
+ if not (keys >= required_keys[segment_type]):
+ missing_keys = required_keys[segment_type] - keys
+ echoerr(
+ context='Error while checking segments (key {key})'.format(key=context.key),
+ context_mark=context[-1][1].mark,
+ problem='found missing required keys: {0}'.format(
+ list_sep.join(missing_keys))
+ )
+ hadproblem = True
+
+ if not (segment_type == 'function' or (keys & highlight_keys)):
+ echoerr(
+ context='Error while checking segments (key {key})'.format(key=context.key),
+ context_mark=context[-1][1].mark,
+ problem=(
+ 'found missing keys required to determine highlight group. '
+ 'Either highlight_groups or name key must be present'
+ )
+ )
+ hadproblem = True
+
+ return True, False, hadproblem
+
+
+def check_segment_module(module, data, context, echoerr):
+ havemarks(module)
+ with WithPath(data['import_paths']):
+ try:
+ __import__(str(module))
+ except ImportError as e:
+ if echoerr.logger.level >= logging.DEBUG:
+ echoerr.logger.exception(e)
+ echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
+ problem='failed to import module {0}'.format(module),
+ problem_mark=module.mark)
+ return True, False, True
+ return True, False, False
+
+
+def check_full_segment_data(segment, data, context, echoerr):
+ if 'name' not in segment and 'function' not in segment:
+ return True, False, False
+
+ ext = data['ext']
+ theme_segment_data = context[0][1].get('segment_data', {})
+ 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(main_theme_name, {}).get('segment_data', {})
+
+ if segment.get('type', 'function') == 'function':
+ function_name = segment.get('function')
+ if function_name:
+ module, function_name = get_function_strings(function_name, context, ext)
+ names = [module + '.' + function_name, function_name]
+ else:
+ names = []
+ elif segment.get('name'):
+ names = [segment['name']]
+ else:
+ return True, False, False
+
+ segment_copy = segment.copy()
+
+ for key in ('before', 'after', 'args', 'contents'):
+ if key not in segment_copy:
+ for segment_data in [theme_segment_data, top_segment_data]:
+ for name in names:
+ try:
+ val = segment_data[name][key]
+ k = segment_data[name].keydict[key]
+ segment_copy[k] = val
+ except KeyError:
+ pass
+
+ return check_key_compatibility(segment_copy, data, context, echoerr)
+
+
+highlight_group_spec = Spec().ident().copy
+_highlight_group_spec = highlight_group_spec().context_message(
+ 'Error while checking function documentation while checking theme (key {key})')
+
+
+def check_hl_group_name(hl_group, context_mark, context, echoerr):
+ '''Check highlight group name: it should match naming conventions
+
+ :param str hl_group:
+ Checked group.
+ :param Mark context_mark:
+ Context mark. May be ``None``.
+ :param Context context:
+ Current context.
+ :param func echoerr:
+ Function used for error reporting.
+
+ :return: ``False`` if check succeeded and ``True`` if it failed.
+ '''
+ return _highlight_group_spec.match(hl_group, context_mark=context_mark, context=context, echoerr=echoerr)[1]
+
+
+def check_segment_function(function_name, data, context, echoerr):
+ havemarks(function_name)
+ ext = data['ext']
+ module, function_name = get_function_strings(function_name, context, ext)
+ if context[-2][1].get('type', 'function') == 'function':
+ func = import_segment(function_name, data, context, echoerr, module=module)
+
+ if not func:
+ return True, False, True
+
+ hl_groups = []
+ divider_hl_group = None
+
+ hadproblem = False
+
+ if func.__doc__:
+ NO_H_G_USED_STR = 'No highlight groups are used (literal segment).'
+ H_G_USED_STR = 'Highlight groups used: '
+ LHGUS = len(H_G_USED_STR)
+ D_H_G_USED_STR = 'Divider highlight group used: '
+ LDHGUS = len(D_H_G_USED_STR)
+ pointer = 0
+ mark_name = '<{0} docstring>'.format(function_name)
+ for i, line in enumerate(func.__doc__.split('\n')):
+ if H_G_USED_STR in line:
+ idx = line.index(H_G_USED_STR) + LHGUS
+ if hl_groups is None:
+ idx -= LHGUS
+ mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ context_mark=function_name.mark,
+ problem=(
+ 'found highlight group definition in addition to sentence stating that '
+ 'no highlight groups are used'
+ ),
+ problem_mark=mark,
+ )
+ hadproblem = True
+ continue
+ hl_groups.append((
+ line[idx:],
+ (mark_name, i + 1, idx + 1, func.__doc__),
+ pointer + idx
+ ))
+ elif D_H_G_USED_STR in line:
+ idx = line.index(D_H_G_USED_STR) + LDHGUS + 2
+ mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
+ divider_hl_group = MarkedUnicode(line[idx:-3], mark)
+ elif NO_H_G_USED_STR in line:
+ idx = line.index(NO_H_G_USED_STR)
+ if hl_groups:
+ mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ context_mark=function_name.mark,
+ problem=(
+ 'found sentence stating that no highlight groups are used '
+ 'in addition to highlight group definition'
+ ),
+ problem_mark=mark,
+ )
+ hadproblem = True
+ continue
+ hl_groups = None
+ pointer += len(line) + len('\n')
+
+ if divider_hl_group:
+ r = hl_exists(divider_hl_group, data, context, echoerr, allow_gradients=True)
+ if r:
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ context_mark=function_name.mark,
+ problem=(
+ 'found highlight group {0} not defined in the following colorschemes: {1}\n'
+ '(Group name was obtained from function documentation.)'
+ ).format(divider_hl_group, list_sep.join(r)),
+ problem_mark=divider_hl_group.mark,
+ )
+ hadproblem = True
+ if check_hl_group_name(divider_hl_group, function_name.mark, context, echoerr):
+ hadproblem = True
+
+ if hl_groups:
+ greg = re.compile(r'``([^`]+)``( \(gradient\))?')
+ parsed_hl_groups = []
+ for line, mark_args, pointer in hl_groups:
+ for s in line.split(', '):
+ required_pack = []
+ sub_pointer = pointer
+ for subs in s.split(' or '):
+ match = greg.match(subs)
+ try:
+ if not match:
+ continue
+ hl_group = MarkedUnicode(
+ match.group(1),
+ Mark(*mark_args, pointer=sub_pointer + match.start(1))
+ )
+ if check_hl_group_name(hl_group, function_name.mark, context, echoerr):
+ hadproblem = True
+ gradient = bool(match.group(2))
+ required_pack.append((hl_group, gradient))
+ finally:
+ sub_pointer += len(subs) + len(' or ')
+ parsed_hl_groups.append(required_pack)
+ pointer += len(s) + len(', ')
+ del hl_group, gradient
+ for required_pack in parsed_hl_groups:
+ rs = [
+ hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False))
+ for hl_group, gradient in required_pack
+ ]
+ if all(rs):
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ problem=(
+ 'found highlight groups list ({0}) with all groups not defined in some colorschemes\n'
+ '(Group names were taken from function documentation.)'
+ ).format(list_sep.join((h[0] for h in required_pack))),
+ problem_mark=function_name.mark
+ )
+ for r, h in zip(rs, required_pack):
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
+ h[0], list_sep.join(r))
+ )
+ hadproblem = True
+ elif hl_groups is not None:
+ r = hl_exists(function_name, data, context, echoerr, allow_gradients=True)
+ if r:
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ problem=(
+ 'found highlight group {0} not defined in the following colorschemes: {1}\n'
+ '(If not specified otherwise in documentation, '
+ 'highlight group for function segments\n'
+ 'is the same as the function name.)'
+ ).format(function_name, list_sep.join(r)),
+ problem_mark=function_name.mark
+ )
+ hadproblem = True
+
+ return True, False, hadproblem
+ elif context[-2][1].get('type') != 'segment_list':
+ if function_name not in context[0][1].get('segment_data', {}):
+ main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None)
+ if data['theme'] == main_theme_name:
+ main_theme = {}
+ else:
+ main_theme = data['ext_theme_configs'].get(main_theme_name, {})
+ if (
+ function_name not in main_theme.get('segment_data', {})
+ and function_name not in data['ext_theme_configs'].get('__main__', {}).get('segment_data', {})
+ and not any(((function_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),
+ problem='found useless use of name key (such name is not present in theme/segment_data)',
+ problem_mark=function_name.mark)
+
+ return True, False, False
+
+
+def hl_group_in_colorscheme(hl_group, cconfig, allow_gradients, data, context, echoerr):
+ havemarks(hl_group, cconfig)
+ if hl_group not in cconfig.get('groups', {}):
+ return False
+ elif not allow_gradients or allow_gradients == 'force':
+ group_config = cconfig['groups'][hl_group]
+ while isinstance(group_config, unicode):
+ try:
+ group_config = cconfig['groups'][group_config]
+ except KeyError:
+ # No such group. Error was already reported when checking
+ # colorschemes.
+ return True
+ havemarks(group_config)
+ hadgradient = False
+ for ckey in ('fg', 'bg'):
+ color = group_config.get(ckey)
+ if not color:
+ # No color. Error was already reported when checking
+ # colorschemes.
+ return True
+ havemarks(color)
+ # Gradients are only allowed for function segments. Note that
+ # whether *either* color or gradient exists should have been
+ # already checked
+ hascolor = color in data['colors_config'].get('colors', {})
+ hasgradient = color in data['colors_config'].get('gradients', {})
+ if hasgradient:
+ hadgradient = True
+ if allow_gradients is False and not hascolor and hasgradient:
+ echoerr(
+ context='Error while checking highlight group in theme (key {key})'.format(
+ key=context.key),
+ context_mark=hl_group.mark,
+ problem='group {0} is using gradient {1} instead of a color'.format(hl_group, color),
+ problem_mark=color.mark
+ )
+ return False
+ if allow_gradients == 'force' and not hadgradient:
+ echoerr(
+ context='Error while checking highlight group in theme (key {key})'.format(
+ key=context.key),
+ context_mark=hl_group.mark,
+ problem='group {0} should have at least one gradient color, but it has no'.format(hl_group),
+ problem_mark=group_config.mark
+ )
+ return False
+ return True
+
+
+def hl_exists(hl_group, data, context, echoerr, allow_gradients=False):
+ havemarks(hl_group)
+ ext = data['ext']
+ if ext not in data['colorscheme_configs']:
+ # No colorschemes. Error was already reported, no need to report it
+ # twice
+ return []
+ r = []
+ found = False
+ for colorscheme, cconfig in data['colorscheme_configs'][ext].items():
+ if hl_group_in_colorscheme(hl_group, cconfig, allow_gradients, data, context, echoerr):
+ found = True
+ else:
+ r.append(colorscheme)
+ if not found:
+ pass
+ return r
+
+
+def check_highlight_group(hl_group, data, context, echoerr):
+ havemarks(hl_group)
+ r = hl_exists(hl_group, data, context, echoerr)
+ if r:
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
+ hl_group, list_sep.join(r)),
+ problem_mark=hl_group.mark
+ )
+ return True, False, True
+ return True, False, False
+
+
+def check_highlight_groups(hl_groups, data, context, echoerr):
+ havemarks(hl_groups)
+ rs = [hl_exists(hl_group, data, context, echoerr) for hl_group in hl_groups]
+ if all(rs):
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format(
+ list_sep.join((unicode(h) for h in hl_groups))),
+ problem_mark=hl_groups.mark
+ )
+ for r, hl_group in zip(rs, hl_groups):
+ echoerr(
+ context='Error while checking theme (key {key})'.format(key=context.key),
+ problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
+ hl_group, list_sep.join(r)),
+ problem_mark=hl_group.mark
+ )
+ return True, False, True
+ return True, False, False
+
+
+def check_segment_data_key(key, data, context, echoerr):
+ havemarks(key)
+ 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 key == segment['name']:
+ found = True
+ break
+ else:
+ function_name = segment.get('function')
+ if function_name:
+ module, function_name = get_function_strings(function_name, ((None, theme),), ext)
+ if has_module_name:
+ full_name = module + '.' + function_name
+ if key == full_name:
+ found = True
+ break
+ else:
+ if key == function_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)
+ return True, False, True
+
+ return True, False, False
+
+
+threaded_args_specs = {
+ 'interval': Spec().cmp('gt', 0.0),
+ 'update_first': Spec().type(bool),
+ 'shutdown_event': Spec().error('Shutdown event must be set by powerline'),
+}
+
+
+def check_args_variant(func, args, data, context, echoerr):
+ havemarks(args)
+ argspec = getconfigargspec(func)
+ present_args = set(args)
+ all_args = set(argspec.args)
+ required_args = set(argspec.args[:-len(argspec.defaults)])
+
+ hadproblem = False
+
+ if required_args - present_args:
+ echoerr(
+ context='Error while checking segment arguments (key {key})'.format(key=context.key),
+ context_mark=args.mark,
+ problem='some of the required keys are missing: {0}'.format(list_sep.join(required_args - present_args))
+ )
+ hadproblem = True
+
+ if not all_args >= present_args:
+ echoerr(context='Error while checking segment arguments (key {key})'.format(key=context.key),
+ context_mark=args.mark,
+ problem='found unknown keys: {0}'.format(list_sep.join(present_args - all_args)),
+ problem_mark=next(iter(present_args - all_args)).mark)
+ hadproblem = True
+
+ if isinstance(func, ThreadedSegment):
+ for key in set(threaded_args_specs) & present_args:
+ proceed, khadproblem = threaded_args_specs[key].match(
+ args[key],
+ args.mark,
+ data,
+ context.enter_key(args, key),
+ echoerr
+ )
+ if khadproblem:
+ hadproblem = True
+ if not proceed:
+ return hadproblem
+
+ return hadproblem
+
+
+def check_args(get_functions, args, data, context, echoerr):
+ new_echoerr = DelayedEchoErr(echoerr)
+ count = 0
+ hadproblem = False
+ for func in get_functions(data, context, new_echoerr):
+ count += 1
+ shadproblem = check_args_variant(func, args, data, context, echoerr)
+ if shadproblem:
+ hadproblem = True
+
+ if not count:
+ hadproblem = True
+ if new_echoerr:
+ new_echoerr.echo_all()
+ else:
+ echoerr(context='Error while checking segment arguments (key {key})'.format(key=context.key),
+ context_mark=context[-2][1].mark,
+ problem='no suitable segments found')
+
+ return True, False, hadproblem
+
+
+def get_one_segment_function(data, context, echoerr):
+ ext = data['ext']
+ function_name = context[-2][1].get('function')
+ if function_name:
+ module, function_name = get_function_strings(function_name, context, ext)
+ func = import_segment(function_name, data, context, echoerr, module=module)
+ if func:
+ yield func
+
+
+common_names = defaultdict(set)
+
+
+def register_common_name(name, cmodule, cname):
+ s = cmodule + '.' + cname
+ cmodule_mark = Mark('<common name definition>', 1, 1, s, 1)
+ cname_mark = Mark('<common name definition>', 1, len(cmodule) + 1, s, len(cmodule) + 1)
+ common_names[name].add((MarkedUnicode(cmodule, cmodule_mark), MarkedUnicode(cname, cname_mark)))
+
+
+def get_all_possible_functions(data, context, echoerr):
+ name = context[-2][0]
+ module, name = name.rpartition('.')[::2]
+ if module:
+ func = import_segment(name, data, context, echoerr, module=module)
+ if func:
+ yield func
+ else:
+ if name in common_names:
+ for cmodule, cname in common_names[name]:
+ cfunc = import_segment(cname, data, context, echoerr, module=MarkedUnicode(cmodule, None))
+ if cfunc:
+ yield cfunc
+ 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':
+ function_name = segment.get('function')
+ current_name = segment.get('name')
+ if function_name:
+ module, function_name = get_function_strings(function_name, ((None, theme_config),), ext)
+ if current_name == name or function_name == name:
+ func = import_segment(function_name, data, context, echoerr, module=module)
+ if func:
+ yield func
+
+
+def check_exinclude_function(name, data, context, echoerr):
+ ext = data['ext']
+ module, name = name.rpartition('.')[::2]
+ if not module:
+ module = MarkedUnicode('powerline.selectors.' + ext, None)
+ func = import_function('selector', name, data, context, echoerr, module=module)
+ if not func:
+ return True, False, True
+ return True, False, False
+
+
+def check_log_file_level(this_level, data, context, echoerr):
+ '''Check handler level specified in :ref:`log_file key <config-common-log>`
+
+ This level must be greater or equal to the level in :ref:`log_level key
+ <config-common-log_level>`.
+ '''
+ havemarks(this_level)
+ hadproblem = False
+ top_level = context[0][1].get('common', {}).get('log_level', 'WARNING')
+ top_level_str = top_level
+ top_level_mark = getattr(top_level, 'mark', None)
+ if (
+ not isinstance(top_level, unicode) or not hasattr(logging, top_level)
+ or not isinstance(this_level, unicode) or not hasattr(logging, this_level)
+ ):
+ return True, False, hadproblem
+ top_level = getattr(logging, top_level)
+ this_level_str = this_level
+ this_level_mark = this_level.mark
+ this_level = getattr(logging, this_level)
+ if this_level < top_level:
+ echoerr(
+ context='Error while checking log level index (key {key})'.format(
+ key=context.key),
+ context_mark=this_level_mark,
+ problem='found level that is less critical then top level ({0} < {0})'.format(
+ this_level_str, top_level_str),
+ problem_mark=top_level_mark,
+ )
+ hadproblem = True
+ return True, False, hadproblem
+
+
+def check_logging_handler(handler_name, data, context, echoerr):
+ havemarks(handler_name)
+ import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])]
+
+ handler_module, separator, handler_class = handler_name.rpartition('.')
+ if not separator:
+ handler_module = 'logging.handlers'
+ handler_class = handler_name
+ with WithPath(import_paths):
+ try:
+ handler = getattr(__import__(str(handler_module), fromlist=[str(handler_class)]), str(handler_class))
+ except ImportError:
+ echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
+ problem='failed to load module {0}'.format(handler_module),
+ problem_mark=handler_name.mark)
+ return True, False, True
+ except AttributeError:
+ echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
+ problem='failed to load handler class {0}'.format(handler_class),
+ problem_mark=handler_name.mark)
+ return True, False, True
+
+ if not issubclass(handler, logging.Handler):
+ echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
+ problem='loaded class {0} is not a logging.Handler subclass'.format(handler_class),
+ problem_mark=handler_name.mark)
+ return True, False, True
+
+ return True, False, False
diff --git a/powerline/lint/context.py b/powerline/lint/context.py
new file mode 100644
index 0000000..a48a283
--- /dev/null
+++ b/powerline/lint/context.py
@@ -0,0 +1,68 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import itertools
+
+from powerline.lib.unicode import unicode
+from powerline.lint.markedjson.markedvalue import MarkedUnicode
+from powerline.lint.selfcheck import havemarks
+
+
+class JStr(unicode):
+ def join(self, iterable):
+ return super(JStr, self).join((unicode(item) for item in iterable))
+
+
+key_sep = JStr('/')
+
+
+def list_themes(data, context):
+ theme_type = data['theme_type']
+ ext = data['ext']
+ 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(*[
+ [(theme_ext, theme) for theme in theme_configs.values()]
+ for theme_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:
+ return [(ext, context[0][1])]
+
+
+class Context(tuple):
+ for func in dir(tuple):
+ if func in ('__getitem__', '__init__', '__getattribute__', '__len__', '__iter__'):
+ continue
+ exec((
+ 'def {0}(self, *args, **kwargs):\n'
+ ' raise TypeError("{0} is not allowed for Context")'
+ ).format(func))
+ del func
+
+ __slots__ = ()
+
+ def __new__(cls, base, context_key=None, context_value=None):
+ if context_key is not None:
+ assert(context_value is not None)
+ assert(type(base) is Context)
+ havemarks(context_key, context_value)
+ return tuple.__new__(cls, tuple.__add__(base, ((context_key, context_value),)))
+ else:
+ havemarks(base)
+ return tuple.__new__(cls, ((MarkedUnicode('', base.mark), base),))
+
+ @property
+ def key(self):
+ return key_sep.join((c[0] for c in self))
+
+ def enter_key(self, value, key):
+ return self.enter(value.keydict[key], value[key])
+
+ def enter_item(self, name, item):
+ return self.enter(MarkedUnicode(name, item.mark), item)
+
+ def enter(self, context_key, context_value):
+ return Context.__new__(Context, self, context_key, context_value)
diff --git a/powerline/lint/imp.py b/powerline/lint/imp.py
new file mode 100644
index 0000000..399654e
--- /dev/null
+++ b/powerline/lint/imp.py
@@ -0,0 +1,56 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+
+from powerline.lint.selfcheck import havemarks
+
+
+class WithPath(object):
+ def __init__(self, import_paths):
+ self.import_paths = import_paths
+
+ def __enter__(self):
+ self.oldpath = sys.path
+ sys.path = self.import_paths + sys.path
+
+ def __exit__(self, *args):
+ sys.path = self.oldpath
+
+
+def import_function(function_type, name, data, context, echoerr, module):
+ havemarks(name, module)
+
+ if module == 'powerline.segments.i3wm' and name == 'workspaces':
+ echoerr(context='Warning while checking segments (key {key})'.format(key=context.key),
+ context_mark=name.mark,
+ problem='segment {0} from {1} is deprecated'.format(name, module),
+ problem_mark=module.mark)
+
+ with WithPath(data['import_paths']):
+ try:
+ func = getattr(__import__(str(module), fromlist=[str(name)]), str(name))
+ except ImportError:
+ echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
+ context_mark=name.mark,
+ problem='failed to import module {0}'.format(module),
+ problem_mark=module.mark)
+ return None
+ except AttributeError:
+ echoerr(context='Error while loading {0} function (key {key})'.format(function_type, key=context.key),
+ problem='failed to load function {0} from module {1}'.format(name, module),
+ problem_mark=name.mark)
+ return None
+
+ if not callable(func):
+ echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
+ context_mark=name.mark,
+ problem='imported “function” {0} from module {1} is not callable'.format(name, module),
+ problem_mark=module.mark)
+ return None
+
+ return func
+
+
+def import_segment(*args, **kwargs):
+ return import_function('segment', *args, **kwargs)
diff --git a/powerline/lint/inspect.py b/powerline/lint/inspect.py
new file mode 100644
index 0000000..15bb610
--- /dev/null
+++ b/powerline/lint/inspect.py
@@ -0,0 +1,63 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from inspect import ArgSpec, getargspec
+
+from powerline.segments import Segment
+
+
+def getconfigargspec(obj):
+ if hasattr(obj, 'powerline_origin'):
+ obj = obj.powerline_origin
+ else:
+ obj = obj
+
+ 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 = hasattr(obj, 'powerline_requires_segment_info')
+ requires_filesystem_watcher = hasattr(obj, 'powerline_requires_filesystem_watcher')
+
+ 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 in omitted_args
+ or arg == 'pl'
+ or arg == '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:
+ 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)
+
+ return ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults))
diff --git a/powerline/lint/markedjson/__init__.py b/powerline/lint/markedjson/__init__.py
new file mode 100644
index 0000000..dea5faf
--- /dev/null
+++ b/powerline/lint/markedjson/__init__.py
@@ -0,0 +1,19 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lint.markedjson.loader import Loader
+
+
+def load(stream, Loader=Loader):
+ '''Parse JSON value and produce the corresponding Python object
+
+ :return:
+ (hadproblem, object) where first argument is true if there were errors
+ during loading JSON stream and second is the corresponding JSON object.
+ '''
+ loader = Loader(stream)
+ try:
+ r = loader.get_single_data()
+ return r, loader.haserrors
+ finally:
+ loader.dispose()
diff --git a/powerline/lint/markedjson/composer.py b/powerline/lint/markedjson/composer.py
new file mode 100644
index 0000000..bd5620d
--- /dev/null
+++ b/powerline/lint/markedjson/composer.py
@@ -0,0 +1,119 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lint.markedjson import nodes
+from powerline.lint.markedjson import events
+from powerline.lint.markedjson.error import MarkedError
+
+
+__all__ = ['Composer', 'ComposerError']
+
+
+class ComposerError(MarkedError):
+ pass
+
+
+class Composer:
+ def __init__(self):
+ pass
+
+ def check_node(self):
+ # Drop the STREAM-START event.
+ if self.check_event(events.StreamStartEvent):
+ self.get_event()
+
+ # If there are more documents available?
+ return not self.check_event(events.StreamEndEvent)
+
+ def get_node(self):
+ # Get the root node of the next document.
+ if not self.check_event(events.StreamEndEvent):
+ return self.compose_document()
+
+ def get_single_node(self):
+ # Drop the STREAM-START event.
+ self.get_event()
+
+ # Compose a document if the stream is not empty.
+ document = None
+ if not self.check_event(events.StreamEndEvent):
+ document = self.compose_document()
+
+ # Ensure that the stream contains no more documents.
+ if not self.check_event(events.StreamEndEvent):
+ event = self.get_event()
+ raise ComposerError(
+ 'expected a single document in the stream',
+ document.start_mark,
+ 'but found another document',
+ event.start_mark
+ )
+
+ # Drop the STREAM-END event.
+ self.get_event()
+
+ return document
+
+ def compose_document(self):
+ # Drop the DOCUMENT-START event.
+ self.get_event()
+
+ # Compose the root node.
+ node = self.compose_node(None, None)
+
+ # Drop the DOCUMENT-END event.
+ self.get_event()
+
+ return node
+
+ def compose_node(self, parent, index):
+ self.descend_resolver(parent, index)
+ if self.check_event(events.ScalarEvent):
+ node = self.compose_scalar_node()
+ elif self.check_event(events.SequenceStartEvent):
+ node = self.compose_sequence_node()
+ elif self.check_event(events.MappingStartEvent):
+ node = self.compose_mapping_node()
+ self.ascend_resolver()
+ return node
+
+ def compose_scalar_node(self):
+ event = self.get_event()
+ tag = event.tag
+ if tag is None or tag == '!':
+ tag = self.resolve(nodes.ScalarNode, event.value, event.implicit, event.start_mark)
+ node = nodes.ScalarNode(tag, event.value, event.start_mark, event.end_mark, style=event.style)
+ return node
+
+ def compose_sequence_node(self):
+ start_event = self.get_event()
+ tag = start_event.tag
+ if tag is None or tag == '!':
+ tag = self.resolve(nodes.SequenceNode, None, start_event.implicit)
+ node = nodes.SequenceNode(tag, [], start_event.start_mark, None, flow_style=start_event.flow_style)
+ index = 0
+ while not self.check_event(events.SequenceEndEvent):
+ node.value.append(self.compose_node(node, index))
+ index += 1
+ end_event = self.get_event()
+ node.end_mark = end_event.end_mark
+ return node
+
+ def compose_mapping_node(self):
+ start_event = self.get_event()
+ tag = start_event.tag
+ if tag is None or tag == '!':
+ tag = self.resolve(nodes.MappingNode, None, start_event.implicit)
+ node = nodes.MappingNode(tag, [], start_event.start_mark, None, flow_style=start_event.flow_style)
+ while not self.check_event(events.MappingEndEvent):
+ # key_event = self.peek_event()
+ item_key = self.compose_node(node, None)
+ # if item_key in node.value:
+ # raise ComposerError('while composing a mapping', start_event.start_mark,
+ # 'found duplicate key', key_event.start_mark)
+ item_value = self.compose_node(node, item_key)
+ # node.value[item_key] = item_value
+ node.value.append((item_key, item_value))
+ end_event = self.get_event()
+ node.end_mark = end_event.end_mark
+ return node
diff --git a/powerline/lint/markedjson/constructor.py b/powerline/lint/markedjson/constructor.py
new file mode 100644
index 0000000..372d84b
--- /dev/null
+++ b/powerline/lint/markedjson/constructor.py
@@ -0,0 +1,285 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import collections
+import types
+
+from functools import wraps
+
+from powerline.lint.markedjson.error import MarkedError
+
+from powerline.lint.markedjson import nodes
+from powerline.lint.markedjson.markedvalue import gen_marked_value
+from powerline.lib.unicode import unicode
+
+
+def marked(func):
+ @wraps(func)
+ def f(self, node, *args, **kwargs):
+ return gen_marked_value(func(self, node, *args, **kwargs), node.start_mark)
+ return f
+
+
+class ConstructorError(MarkedError):
+ pass
+
+
+class BaseConstructor:
+ yaml_constructors = {}
+
+ def __init__(self):
+ self.constructed_objects = {}
+ self.state_generators = []
+ self.deep_construct = False
+
+ def check_data(self):
+ # If there are more documents available?
+ return self.check_node()
+
+ def get_data(self):
+ # Construct and return the next document.
+ if self.check_node():
+ return self.construct_document(self.get_node())
+
+ def get_single_data(self):
+ # Ensure that the stream contains a single document and construct it.
+ node = self.get_single_node()
+ if node is not None:
+ return self.construct_document(node)
+ return None
+
+ def construct_document(self, node):
+ data = self.construct_object(node)
+ while self.state_generators:
+ state_generators = self.state_generators
+ self.state_generators = []
+ for generator in state_generators:
+ for dummy in generator:
+ pass
+ self.constructed_objects = {}
+ self.deep_construct = False
+ return data
+
+ def construct_object(self, node, deep=False):
+ if node in self.constructed_objects:
+ return self.constructed_objects[node]
+ if deep:
+ old_deep = self.deep_construct
+ self.deep_construct = True
+ constructor = None
+ tag_suffix = None
+ if node.tag in self.yaml_constructors:
+ constructor = self.yaml_constructors[node.tag]
+ else:
+ raise ConstructorError(None, None, 'no constructor for tag %s' % node.tag)
+ if tag_suffix is None:
+ data = constructor(self, node)
+ else:
+ data = constructor(self, tag_suffix, node)
+ if isinstance(data, types.GeneratorType):
+ generator = data
+ data = next(generator)
+ if self.deep_construct:
+ for dummy in generator:
+ pass
+ else:
+ self.state_generators.append(generator)
+ self.constructed_objects[node] = data
+ if deep:
+ self.deep_construct = old_deep
+ return data
+
+ @marked
+ def construct_scalar(self, node):
+ if not isinstance(node, nodes.ScalarNode):
+ raise ConstructorError(
+ None, None,
+ 'expected a scalar node, but found %s' % node.id,
+ node.start_mark
+ )
+ return node.value
+
+ def construct_sequence(self, node, deep=False):
+ if not isinstance(node, nodes.SequenceNode):
+ raise ConstructorError(
+ None, None,
+ 'expected a sequence node, but found %s' % node.id,
+ node.start_mark
+ )
+ return [
+ self.construct_object(child, deep=deep)
+ for child in node.value
+ ]
+
+ @marked
+ def construct_mapping(self, node, deep=False):
+ if not isinstance(node, nodes.MappingNode):
+ raise ConstructorError(
+ None, None,
+ 'expected a mapping node, but found %s' % node.id,
+ node.start_mark
+ )
+ mapping = {}
+ for key_node, value_node in node.value:
+ key = self.construct_object(key_node, deep=deep)
+ if not isinstance(key, collections.abc.Hashable):
+ self.echoerr(
+ 'While constructing a mapping', node.start_mark,
+ 'found unhashable key', key_node.start_mark
+ )
+ continue
+ elif type(key.value) != unicode:
+ self.echoerr(
+ 'Error while constructing a mapping', node.start_mark,
+ 'found key that is not a string', key_node.start_mark
+ )
+ continue
+ elif key in mapping:
+ self.echoerr(
+ 'Error while constructing a mapping', node.start_mark,
+ 'found duplicate key', key_node.start_mark
+ )
+ continue
+ value = self.construct_object(value_node, deep=deep)
+ mapping[key] = value
+ return mapping
+
+ @classmethod
+ def add_constructor(cls, tag, constructor):
+ if 'yaml_constructors' not in cls.__dict__:
+ cls.yaml_constructors = cls.yaml_constructors.copy()
+ cls.yaml_constructors[tag] = constructor
+
+
+class Constructor(BaseConstructor):
+ def construct_scalar(self, node):
+ if isinstance(node, nodes.MappingNode):
+ for key_node, value_node in node.value:
+ if key_node.tag == 'tag:yaml.org,2002:value':
+ return self.construct_scalar(value_node)
+ return BaseConstructor.construct_scalar(self, node)
+
+ def flatten_mapping(self, node):
+ merge = []
+ index = 0
+ while index < len(node.value):
+ key_node, value_node = node.value[index]
+ if key_node.tag == 'tag:yaml.org,2002:merge':
+ del node.value[index]
+ if isinstance(value_node, nodes.MappingNode):
+ self.flatten_mapping(value_node)
+ merge.extend(value_node.value)
+ elif isinstance(value_node, nodes.SequenceNode):
+ submerge = []
+ for subnode in value_node.value:
+ if not isinstance(subnode, nodes.MappingNode):
+ raise ConstructorError(
+ 'while constructing a mapping',
+ node.start_mark,
+ 'expected a mapping for merging, but found %s' % subnode.id,
+ subnode.start_mark
+ )
+ self.flatten_mapping(subnode)
+ submerge.append(subnode.value)
+ submerge.reverse()
+ for value in submerge:
+ merge.extend(value)
+ else:
+ raise ConstructorError(
+ 'while constructing a mapping',
+ node.start_mark,
+ ('expected a mapping or list of mappings for merging, but found %s' % value_node.id),
+ value_node.start_mark
+ )
+ elif key_node.tag == 'tag:yaml.org,2002:value':
+ key_node.tag = 'tag:yaml.org,2002:str'
+ index += 1
+ else:
+ index += 1
+ if merge:
+ node.value = merge + node.value
+
+ def construct_mapping(self, node, deep=False):
+ if isinstance(node, nodes.MappingNode):
+ self.flatten_mapping(node)
+ return BaseConstructor.construct_mapping(self, node, deep=deep)
+
+ @marked
+ def construct_yaml_null(self, node):
+ self.construct_scalar(node)
+ return None
+
+ @marked
+ def construct_yaml_bool(self, node):
+ value = self.construct_scalar(node).value
+ return bool(value)
+
+ @marked
+ def construct_yaml_int(self, node):
+ value = self.construct_scalar(node).value
+ sign = +1
+ if value[0] == '-':
+ sign = -1
+ if value[0] in '+-':
+ value = value[1:]
+ if value == '0':
+ return 0
+ else:
+ return sign * int(value)
+
+ @marked
+ def construct_yaml_float(self, node):
+ value = self.construct_scalar(node).value
+ sign = +1
+ if value[0] == '-':
+ sign = -1
+ if value[0] in '+-':
+ value = value[1:]
+ else:
+ return sign * float(value)
+
+ def construct_yaml_str(self, node):
+ return self.construct_scalar(node)
+
+ def construct_yaml_seq(self, node):
+ data = gen_marked_value([], node.start_mark)
+ yield data
+ data.extend(self.construct_sequence(node))
+
+ def construct_yaml_map(self, node):
+ data = gen_marked_value({}, node.start_mark)
+ yield data
+ value = self.construct_mapping(node)
+ data.update(value)
+
+ def construct_undefined(self, node):
+ raise ConstructorError(
+ None, None,
+ 'could not determine a constructor for the tag %r' % node.tag,
+ node.start_mark
+ )
+
+
+Constructor.add_constructor(
+ 'tag:yaml.org,2002:null', Constructor.construct_yaml_null)
+
+Constructor.add_constructor(
+ 'tag:yaml.org,2002:bool', Constructor.construct_yaml_bool)
+
+Constructor.add_constructor(
+ 'tag:yaml.org,2002:int', Constructor.construct_yaml_int)
+
+Constructor.add_constructor(
+ 'tag:yaml.org,2002:float', Constructor.construct_yaml_float)
+
+Constructor.add_constructor(
+ 'tag:yaml.org,2002:str', Constructor.construct_yaml_str)
+
+Constructor.add_constructor(
+ 'tag:yaml.org,2002:seq', Constructor.construct_yaml_seq)
+
+Constructor.add_constructor(
+ 'tag:yaml.org,2002:map', Constructor.construct_yaml_map)
+
+Constructor.add_constructor(
+ None, Constructor.construct_undefined)
diff --git a/powerline/lint/markedjson/error.py b/powerline/lint/markedjson/error.py
new file mode 100644
index 0000000..732120b
--- /dev/null
+++ b/powerline/lint/markedjson/error.py
@@ -0,0 +1,241 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import re
+
+from powerline.lib.encoding import get_preferred_output_encoding
+
+
+NON_PRINTABLE_STR = (
+ '[^'
+ # ASCII control characters: 0x00-0x19
+ + '\t\n' # Tab, newline: allowed ASCII control characters
+ + '\x20-\x7E' # ASCII printable characters
+ # Unicode control characters: 0x7F-0x9F
+ + '\u0085' # Allowed unicode control character: next line character
+ + '\u00A0-\uD7FF'
+ # Surrogate escapes: 0xD800-0xDFFF
+ + '\uE000-\uFFFD'
+ + ((
+ '\uD800-\uDFFF'
+ ) if sys.maxunicode < 0x10FFFF else (
+ '\U00010000-\U0010FFFF'
+ ))
+ + ']'
+ + ((
+ # Paired surrogate escapes: allowed in UCS-2 builds as the only way to
+ # represent characters above 0xFFFF. Only paired variant is allowed.
+ '|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]'
+ + '|[\uD800-\uDBFF](?![\uDC00-\uDFFF])'
+ ) if sys.maxunicode < 0x10FFFF else (
+ ''
+ ))
+)
+NON_PRINTABLE_RE = re.compile(NON_PRINTABLE_STR)
+
+
+def repl(s):
+ return '<x%04x>' % ord(s.group())
+
+
+def strtrans(s):
+ return NON_PRINTABLE_RE.sub(repl, s.replace('\t', '>---'))
+
+
+class Mark:
+ def __init__(self, name, line, column, buffer, pointer, old_mark=None, merged_marks=None):
+ self.name = name
+ self.line = line
+ self.column = column
+ self.buffer = buffer
+ self.pointer = pointer
+ self.old_mark = old_mark
+ self.merged_marks = merged_marks or []
+
+ def copy(self):
+ return Mark(self.name, self.line, self.column, self.buffer, self.pointer, self.old_mark, self.merged_marks[:])
+
+ def get_snippet(self, indent=4, max_length=75):
+ if self.buffer is None:
+ return None
+ head = ''
+ start = self.pointer
+ while start > 0 and self.buffer[start - 1] not in '\0\n':
+ start -= 1
+ if self.pointer - start > max_length / 2 - 1:
+ head = ' ... '
+ start += 5
+ break
+ tail = ''
+ end = self.pointer
+ while end < len(self.buffer) and self.buffer[end] not in '\0\n':
+ end += 1
+ if end - self.pointer > max_length / 2 - 1:
+ tail = ' ... '
+ end -= 5
+ break
+ snippet = [self.buffer[start:self.pointer], self.buffer[self.pointer], self.buffer[self.pointer + 1:end]]
+ snippet = [strtrans(s) for s in snippet]
+ return (
+ ' ' * indent + head + ''.join(snippet) + tail + '\n'
+ + ' ' * (indent + len(head) + len(snippet[0])) + '^'
+ )
+
+ def advance_string(self, diff):
+ ret = self.copy()
+ # FIXME Currently does not work properly with escaped strings.
+ ret.column += diff
+ ret.pointer += diff
+ return ret
+
+ def set_old_mark(self, old_mark):
+ if self is old_mark:
+ return
+ checked_marks = set([id(self)])
+ older_mark = old_mark
+ while True:
+ if id(older_mark) in checked_marks:
+ raise ValueError('Trying to set recursive marks')
+ checked_marks.add(id(older_mark))
+ older_mark = older_mark.old_mark
+ if not older_mark:
+ break
+ self.old_mark = old_mark
+
+ def set_merged_mark(self, merged_mark):
+ self.merged_marks.append(merged_mark)
+
+ def to_string(self, indent=0, head_text='in ', add_snippet=True):
+ mark = self
+ where = ''
+ processed_marks = set()
+ while mark:
+ indentstr = ' ' * indent
+ where += ('%s %s"%s", line %d, column %d' % (
+ indentstr, head_text, mark.name, mark.line + 1, mark.column + 1))
+ if add_snippet:
+ snippet = mark.get_snippet(indent=(indent + 4))
+ if snippet:
+ where += ':\n' + snippet
+ if mark.merged_marks:
+ where += '\n' + indentstr + ' with additionally merged\n'
+ where += mark.merged_marks[0].to_string(indent + 4, head_text='', add_snippet=False)
+ for mmark in mark.merged_marks[1:]:
+ where += '\n' + indentstr + ' and\n'
+ where += mmark.to_string(indent + 4, head_text='', add_snippet=False)
+ if add_snippet:
+ processed_marks.add(id(mark))
+ if mark.old_mark:
+ where += '\n' + indentstr + ' which replaced value\n'
+ indent += 4
+ mark = mark.old_mark
+ if id(mark) in processed_marks:
+ raise ValueError('Trying to dump recursive mark')
+ return where
+
+ if sys.version_info < (3,):
+ def __str__(self):
+ return self.to_string().encode('utf-8')
+
+ def __unicode__(self):
+ return self.to_string()
+ else:
+ def __str__(self):
+ return self.to_string()
+
+ def __eq__(self, other):
+ return self is other or (
+ self.name == other.name
+ and self.line == other.line
+ and self.column == other.column
+ )
+
+
+if sys.version_info < (3,):
+ def echoerr(**kwargs):
+ stream = kwargs.pop('stream', sys.stderr)
+ stream.write('\n')
+ stream.write((format_error(**kwargs) + '\n').encode(get_preferred_output_encoding()))
+else:
+ def echoerr(**kwargs):
+ stream = kwargs.pop('stream', sys.stderr)
+ stream.write('\n')
+ stream.write(format_error(**kwargs) + '\n')
+
+
+def format_error(context=None, context_mark=None, problem=None, problem_mark=None, note=None, indent=0):
+ lines = []
+ indentstr = ' ' * indent
+ if context is not None:
+ lines.append(indentstr + context)
+ if (
+ context_mark is not None
+ and (
+ problem is None or problem_mark is None
+ or context_mark != problem_mark
+ )
+ ):
+ lines.append(context_mark.to_string(indent=indent))
+ if problem is not None:
+ lines.append(indentstr + problem)
+ if problem_mark is not None:
+ lines.append(problem_mark.to_string(indent=indent))
+ if note is not None:
+ lines.append(indentstr + note)
+ return '\n'.join(lines)
+
+
+class MarkedError(Exception):
+ def __init__(self, context=None, context_mark=None, problem=None, problem_mark=None, note=None):
+ Exception.__init__(self, format_error(context, context_mark, problem, problem_mark, note))
+
+
+class EchoErr(object):
+ __slots__ = ('echoerr', 'logger', 'indent')
+
+ def __init__(self, echoerr, logger, indent=0):
+ self.echoerr = echoerr
+ self.logger = logger
+ self.indent = indent
+
+ def __call__(self, **kwargs):
+ kwargs = kwargs.copy()
+ kwargs.setdefault('indent', self.indent)
+ self.echoerr(**kwargs)
+
+
+class DelayedEchoErr(EchoErr):
+ __slots__ = ('echoerr', 'logger', 'errs', 'message', 'separator_message', 'indent', 'indent_shift')
+
+ def __init__(self, echoerr, message='', separator_message=''):
+ super(DelayedEchoErr, self).__init__(echoerr, echoerr.logger)
+ self.errs = [[]]
+ self.message = message
+ self.separator_message = separator_message
+ self.indent_shift = (4 if message or separator_message else 0)
+ self.indent = echoerr.indent + self.indent_shift
+
+ def __call__(self, **kwargs):
+ kwargs = kwargs.copy()
+ kwargs['indent'] = kwargs.get('indent', 0) + self.indent
+ self.errs[-1].append(kwargs)
+
+ def next_variant(self):
+ self.errs.append([])
+
+ def echo_all(self):
+ if self.message:
+ self.echoerr(problem=self.message, indent=(self.indent - self.indent_shift))
+ for variant in self.errs:
+ if not variant:
+ continue
+ if self.separator_message and variant is not self.errs[0]:
+ self.echoerr(problem=self.separator_message, indent=(self.indent - self.indent_shift))
+ for kwargs in variant:
+ self.echoerr(**kwargs)
+
+ def __nonzero__(self):
+ return not not self.errs
+
+ __bool__ = __nonzero__
diff --git a/powerline/lint/markedjson/events.py b/powerline/lint/markedjson/events.py
new file mode 100644
index 0000000..ef8a70e
--- /dev/null
+++ b/powerline/lint/markedjson/events.py
@@ -0,0 +1,97 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+
+# Abstract classes.
+class Event(object):
+ def __init__(self, start_mark=None, end_mark=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+ def __repr__(self):
+ attributes = [
+ key for key in ['implicit', 'value']
+ if hasattr(self, key)
+ ]
+ arguments = ', '.join([
+ '%s=%r' % (key, getattr(self, key))
+ for key in attributes
+ ])
+ return '%s(%s)' % (self.__class__.__name__, arguments)
+
+
+class NodeEvent(Event):
+ def __init__(self, start_mark=None, end_mark=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+
+class CollectionStartEvent(NodeEvent):
+ def __init__(self, implicit, start_mark=None, end_mark=None, flow_style=None):
+ self.tag = None
+ self.implicit = implicit
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.flow_style = flow_style
+
+
+class CollectionEndEvent(Event):
+ pass
+
+
+# Implementations.
+class StreamStartEvent(Event):
+ def __init__(self, start_mark=None, end_mark=None, encoding=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.encoding = encoding
+
+
+class StreamEndEvent(Event):
+ pass
+
+
+class DocumentStartEvent(Event):
+ def __init__(self, start_mark=None, end_mark=None, explicit=None, version=None, tags=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.explicit = explicit
+ self.version = version
+ self.tags = tags
+
+
+class DocumentEndEvent(Event):
+ def __init__(self, start_mark=None, end_mark=None, explicit=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.explicit = explicit
+
+
+class AliasEvent(NodeEvent):
+ pass
+
+
+class ScalarEvent(NodeEvent):
+ def __init__(self, implicit, value, start_mark=None, end_mark=None, style=None):
+ self.tag = None
+ self.implicit = implicit
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.style = style
+
+
+class SequenceStartEvent(CollectionStartEvent):
+ pass
+
+
+class SequenceEndEvent(CollectionEndEvent):
+ pass
+
+
+class MappingStartEvent(CollectionStartEvent):
+ pass
+
+
+class MappingEndEvent(CollectionEndEvent):
+ pass
diff --git a/powerline/lint/markedjson/loader.py b/powerline/lint/markedjson/loader.py
new file mode 100644
index 0000000..3ee5686
--- /dev/null
+++ b/powerline/lint/markedjson/loader.py
@@ -0,0 +1,25 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lint.markedjson.reader import Reader
+from powerline.lint.markedjson.scanner import Scanner
+from powerline.lint.markedjson.parser import Parser
+from powerline.lint.markedjson.composer import Composer
+from powerline.lint.markedjson.constructor import Constructor
+from powerline.lint.markedjson.resolver import Resolver
+from powerline.lint.markedjson.error import echoerr
+
+
+class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
+ def __init__(self, stream):
+ Reader.__init__(self, stream)
+ Scanner.__init__(self)
+ Parser.__init__(self)
+ Composer.__init__(self)
+ Constructor.__init__(self)
+ Resolver.__init__(self)
+ self.haserrors = False
+
+ def echoerr(self, *args, **kwargs):
+ echoerr(*args, **kwargs)
+ self.haserrors = True
diff --git a/powerline/lint/markedjson/markedvalue.py b/powerline/lint/markedjson/markedvalue.py
new file mode 100644
index 0000000..3b8db3e
--- /dev/null
+++ b/powerline/lint/markedjson/markedvalue.py
@@ -0,0 +1,151 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lib.unicode import unicode
+
+
+def gen_new(cls):
+ def __new__(arg_cls, value, mark):
+ r = super(arg_cls, arg_cls).__new__(arg_cls, value)
+ r.mark = mark
+ r.value = value
+ return r
+ return __new__
+
+
+def gen_init(cls):
+ def __init__(self, value, mark):
+ return cls.__init__(self, value)
+ return __init__
+
+
+def gen_getnewargs(cls):
+ def __getnewargs__(self):
+ return (self.value, self.mark)
+ return __getnewargs__
+
+
+class MarkedUnicode(unicode):
+ __new__ = gen_new(unicode)
+ __getnewargs__ = gen_getnewargs(unicode)
+
+ def _proc_partition(self, part_result):
+ pointdiff = 1
+ r = []
+ for s in part_result:
+ r.append(MarkedUnicode(s, self.mark.advance_string(pointdiff)))
+ pointdiff += len(s)
+ return tuple(r)
+
+ def rpartition(self, sep):
+ return self._proc_partition(super(MarkedUnicode, self).rpartition(sep))
+
+ def partition(self, sep):
+ return self._proc_partition(super(MarkedUnicode, self).partition(sep))
+
+
+class MarkedInt(int):
+ __new__ = gen_new(int)
+ __getnewargs__ = gen_getnewargs(int)
+
+
+class MarkedFloat(float):
+ __new__ = gen_new(float)
+ __getnewargs__ = gen_getnewargs(float)
+
+
+class MarkedDict(dict):
+ __init__ = gen_init(dict)
+ __getnewargs__ = gen_getnewargs(dict)
+
+ def __new__(arg_cls, value, mark):
+ r = super(arg_cls, arg_cls).__new__(arg_cls, value)
+ r.mark = mark
+ r.value = value
+ r.keydict = dict(((key, key) for key in r))
+ return r
+
+ def setmerged(self, d):
+ try:
+ self.mark.set_merged_mark(d.mark)
+ except AttributeError:
+ pass
+
+ def __setitem__(self, key, value):
+ try:
+ old_value = self[key]
+ except KeyError:
+ pass
+ else:
+ try:
+ key.mark.set_old_mark(self.keydict[key].mark)
+ except AttributeError:
+ pass
+ except KeyError:
+ pass
+ try:
+ value.mark.set_old_mark(old_value.mark)
+ except AttributeError:
+ pass
+ dict.__setitem__(self, key, value)
+ self.keydict[key] = key
+
+ def update(self, *args, **kwargs):
+ dict.update(self, *args, **kwargs)
+ self.keydict = dict(((key, key) for key in self))
+
+ def copy(self):
+ return MarkedDict(super(MarkedDict, self).copy(), self.mark)
+
+
+class MarkedList(list):
+ __new__ = gen_new(list)
+ __init__ = gen_init(list)
+ __getnewargs__ = gen_getnewargs(list)
+
+
+class MarkedValue:
+ def __init__(self, value, mark):
+ self.mark = mark
+ self.value = value
+
+ __getinitargs__ = gen_getnewargs(None)
+
+
+specialclasses = {
+ unicode: MarkedUnicode,
+ int: MarkedInt,
+ float: MarkedFloat,
+ dict: MarkedDict,
+ list: MarkedList,
+}
+
+classcache = {}
+
+
+def gen_marked_value(value, mark, use_special_classes=True):
+ if use_special_classes and value.__class__ in specialclasses:
+ Marked = specialclasses[value.__class__]
+ elif value.__class__ in classcache:
+ Marked = classcache[value.__class__]
+ else:
+ class Marked(MarkedValue):
+ for func in value.__class__.__dict__:
+ if func == 'copy':
+ def copy(self):
+ return self.__class__(self.value.copy(), self.mark)
+ elif func not in set(('__init__', '__new__', '__getattribute__')):
+ if func in set(('__eq__',)):
+ # HACK to make marked dictionaries always work
+ exec ((
+ 'def {0}(self, *args):\n'
+ ' return self.value.{0}(*[arg.value if isinstance(arg, MarkedValue) else arg for arg in args])'
+ ).format(func))
+ else:
+ exec ((
+ 'def {0}(self, *args, **kwargs):\n'
+ ' return self.value.{0}(*args, **kwargs)\n'
+ ).format(func))
+ classcache[value.__class__] = Marked
+
+ return Marked(value, mark)
diff --git a/powerline/lint/markedjson/nodes.py b/powerline/lint/markedjson/nodes.py
new file mode 100644
index 0000000..66ad843
--- /dev/null
+++ b/powerline/lint/markedjson/nodes.py
@@ -0,0 +1,55 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+
+class Node(object):
+ def __init__(self, tag, value, start_mark, end_mark):
+ self.tag = tag
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+ def __repr__(self):
+ value = self.value
+ # if isinstance(value, list):
+ # if len(value) == 0:
+ # value = '<empty>'
+ # elif len(value) == 1:
+ # value = '<1 item>'
+ # else:
+ # value = '<%d items>' % len(value)
+ # else:
+ # if len(value) > 75:
+ # value = repr(value[:70]+u' ... ')
+ # else:
+ # value = repr(value)
+ value = repr(value)
+ return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value)
+
+
+class ScalarNode(Node):
+ id = 'scalar'
+
+ def __init__(self, tag, value, start_mark=None, end_mark=None, style=None):
+ self.tag = tag
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.style = style
+
+
+class CollectionNode(Node):
+ def __init__(self, tag, value, start_mark=None, end_mark=None, flow_style=None):
+ self.tag = tag
+ self.value = value
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.flow_style = flow_style
+
+
+class SequenceNode(CollectionNode):
+ id = 'sequence'
+
+
+class MappingNode(CollectionNode):
+ id = 'mapping'
diff --git a/powerline/lint/markedjson/parser.py b/powerline/lint/markedjson/parser.py
new file mode 100644
index 0000000..336a2a2
--- /dev/null
+++ b/powerline/lint/markedjson/parser.py
@@ -0,0 +1,255 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lint.markedjson.error import MarkedError
+from powerline.lint.markedjson import tokens
+from powerline.lint.markedjson import events
+
+
+class ParserError(MarkedError):
+ pass
+
+
+class Parser:
+ def __init__(self):
+ self.current_event = None
+ self.yaml_version = None
+ self.states = []
+ self.marks = []
+ self.state = self.parse_stream_start
+
+ def dispose(self):
+ # Reset the state attributes (to clear self-references)
+ self.states = []
+ self.state = None
+
+ def check_event(self, *choices):
+ # Check the type of the next event.
+ if self.current_event is None:
+ if self.state:
+ self.current_event = self.state()
+ if self.current_event is not None:
+ if not choices:
+ return True
+ for choice in choices:
+ if isinstance(self.current_event, choice):
+ return True
+ return False
+
+ def peek_event(self):
+ # Get the next event.
+ if self.current_event is None:
+ if self.state:
+ self.current_event = self.state()
+ return self.current_event
+
+ def get_event(self):
+ # Get the next event and proceed further.
+ if self.current_event is None:
+ if self.state:
+ self.current_event = self.state()
+ value = self.current_event
+ self.current_event = None
+ return value
+
+ # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
+ # implicit_document ::= block_node DOCUMENT-END*
+ # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+
+ def parse_stream_start(self):
+ # Parse the stream start.
+ token = self.get_token()
+ event = events.StreamStartEvent(token.start_mark, token.end_mark, encoding=token.encoding)
+
+ # Prepare the next state.
+ self.state = self.parse_implicit_document_start
+
+ return event
+
+ def parse_implicit_document_start(self):
+ # Parse an implicit document.
+ if not self.check_token(tokens.StreamEndToken):
+ token = self.peek_token()
+ start_mark = end_mark = token.start_mark
+ event = events.DocumentStartEvent(start_mark, end_mark, explicit=False)
+
+ # Prepare the next state.
+ self.states.append(self.parse_document_end)
+ self.state = self.parse_node
+
+ return event
+
+ else:
+ return self.parse_document_start()
+
+ def parse_document_start(self):
+ # Parse an explicit document.
+ if not self.check_token(tokens.StreamEndToken):
+ token = self.peek_token()
+ self.echoerr(
+ None, None,
+ ('expected \'<stream end>\', but found %r' % token.id), token.start_mark
+ )
+ return events.StreamEndEvent(token.start_mark, token.end_mark)
+ else:
+ # Parse the end of the stream.
+ token = self.get_token()
+ event = events.StreamEndEvent(token.start_mark, token.end_mark)
+ assert not self.states
+ assert not self.marks
+ self.state = None
+ return event
+
+ def parse_document_end(self):
+ # Parse the document end.
+ token = self.peek_token()
+ start_mark = end_mark = token.start_mark
+ explicit = False
+ event = events.DocumentEndEvent(start_mark, end_mark, explicit=explicit)
+
+ # Prepare the next state.
+ self.state = self.parse_document_start
+
+ return event
+
+ def parse_document_content(self):
+ return self.parse_node()
+
+ def parse_node(self, indentless_sequence=False):
+ start_mark = end_mark = None
+ if start_mark is None:
+ start_mark = end_mark = self.peek_token().start_mark
+ event = None
+ implicit = True
+ if self.check_token(tokens.ScalarToken):
+ token = self.get_token()
+ end_mark = token.end_mark
+ if token.plain:
+ implicit = (True, False)
+ else:
+ implicit = (False, True)
+ event = events.ScalarEvent(implicit, token.value, start_mark, end_mark, style=token.style)
+ self.state = self.states.pop()
+ elif self.check_token(tokens.FlowSequenceStartToken):
+ end_mark = self.peek_token().end_mark
+ event = events.SequenceStartEvent(implicit, start_mark, end_mark, flow_style=True)
+ self.state = self.parse_flow_sequence_first_entry
+ elif self.check_token(tokens.FlowMappingStartToken):
+ end_mark = self.peek_token().end_mark
+ event = events.MappingStartEvent(implicit, start_mark, end_mark, flow_style=True)
+ self.state = self.parse_flow_mapping_first_key
+ else:
+ token = self.peek_token()
+ raise ParserError(
+ 'while parsing a flow node', start_mark,
+ 'expected the node content, but found %r' % token.id,
+ token.start_mark
+ )
+ return event
+
+ def parse_flow_sequence_first_entry(self):
+ token = self.get_token()
+ self.marks.append(token.start_mark)
+ return self.parse_flow_sequence_entry(first=True)
+
+ def parse_flow_sequence_entry(self, first=False):
+ if not self.check_token(tokens.FlowSequenceEndToken):
+ if not first:
+ if self.check_token(tokens.FlowEntryToken):
+ self.get_token()
+ if self.check_token(tokens.FlowSequenceEndToken):
+ token = self.peek_token()
+ self.echoerr(
+ 'While parsing a flow sequence', self.marks[-1],
+ ('expected sequence value, but got %r' % token.id), token.start_mark
+ )
+ else:
+ token = self.peek_token()
+ raise ParserError(
+ 'while parsing a flow sequence', self.marks[-1],
+ ('expected \',\' or \']\', but got %r' % token.id), token.start_mark
+ )
+
+ if not self.check_token(tokens.FlowSequenceEndToken):
+ self.states.append(self.parse_flow_sequence_entry)
+ return self.parse_node()
+ token = self.get_token()
+ event = events.SequenceEndEvent(token.start_mark, token.end_mark)
+ self.state = self.states.pop()
+ self.marks.pop()
+ return event
+
+ def parse_flow_sequence_entry_mapping_end(self):
+ self.state = self.parse_flow_sequence_entry
+ token = self.peek_token()
+ return events.MappingEndEvent(token.start_mark, token.start_mark)
+
+ def parse_flow_mapping_first_key(self):
+ token = self.get_token()
+ self.marks.append(token.start_mark)
+ return self.parse_flow_mapping_key(first=True)
+
+ def parse_flow_mapping_key(self, first=False):
+ if not self.check_token(tokens.FlowMappingEndToken):
+ if not first:
+ if self.check_token(tokens.FlowEntryToken):
+ self.get_token()
+ if self.check_token(tokens.FlowMappingEndToken):
+ token = self.peek_token()
+ self.echoerr(
+ 'While parsing a flow mapping', self.marks[-1],
+ ('expected mapping key, but got %r' % token.id), token.start_mark
+ )
+ else:
+ token = self.peek_token()
+ raise ParserError(
+ 'while parsing a flow mapping', self.marks[-1],
+ ('expected \',\' or \'}\', but got %r' % token.id), token.start_mark
+ )
+ if self.check_token(tokens.KeyToken):
+ token = self.get_token()
+ if not self.check_token(tokens.ValueToken, tokens.FlowEntryToken, tokens.FlowMappingEndToken):
+ self.states.append(self.parse_flow_mapping_value)
+ return self.parse_node()
+ else:
+ token = self.peek_token()
+ raise ParserError(
+ 'while parsing a flow mapping', self.marks[-1],
+ ('expected value, but got %r' % token.id), token.start_mark
+ )
+ elif not self.check_token(tokens.FlowMappingEndToken):
+ token = self.peek_token()
+ expect_key = self.check_token(tokens.ValueToken, tokens.FlowEntryToken)
+ if not expect_key:
+ self.get_token()
+ expect_key = self.check_token(tokens.ValueToken)
+
+ if expect_key:
+ raise ParserError(
+ 'while parsing a flow mapping', self.marks[-1],
+ ('expected string key, but got %r' % token.id), token.start_mark
+ )
+ else:
+ token = self.peek_token()
+ raise ParserError(
+ 'while parsing a flow mapping', self.marks[-1],
+ ('expected \':\', but got %r' % token.id), token.start_mark
+ )
+ token = self.get_token()
+ event = events.MappingEndEvent(token.start_mark, token.end_mark)
+ self.state = self.states.pop()
+ self.marks.pop()
+ return event
+
+ def parse_flow_mapping_value(self):
+ if self.check_token(tokens.ValueToken):
+ token = self.get_token()
+ if not self.check_token(tokens.FlowEntryToken, tokens.FlowMappingEndToken):
+ self.states.append(self.parse_flow_mapping_key)
+ return self.parse_node()
+
+ token = self.peek_token()
+ raise ParserError(
+ 'while parsing a flow mapping', self.marks[-1],
+ ('expected mapping value, but got %r' % token.id), token.start_mark
+ )
diff --git a/powerline/lint/markedjson/reader.py b/powerline/lint/markedjson/reader.py
new file mode 100644
index 0000000..0ca4516
--- /dev/null
+++ b/powerline/lint/markedjson/reader.py
@@ -0,0 +1,141 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import codecs
+
+from powerline.lint.markedjson.error import MarkedError, Mark, NON_PRINTABLE_RE
+from powerline.lib.unicode import unicode
+
+
+# This module contains abstractions for the input stream. You don’t have to
+# looks further, there are no pretty code.
+
+
+class ReaderError(MarkedError):
+ pass
+
+
+class Reader(object):
+ # Reader:
+ # - determines the data encoding and converts it to a unicode string,
+ # - checks if characters are in allowed range,
+ # - adds '\0' to the end.
+
+ # Reader accepts
+ # - a file-like object with its `read` method returning `str`,
+
+ # Yeah, it’s ugly and slow.
+ def __init__(self, stream):
+ self.name = None
+ self.stream = None
+ self.stream_pointer = 0
+ self.eof = True
+ self.buffer = ''
+ self.pointer = 0
+ self.full_buffer = unicode('')
+ self.full_pointer = 0
+ self.raw_buffer = None
+ self.raw_decode = codecs.utf_8_decode
+ self.encoding = 'utf-8'
+ self.index = 0
+ self.line = 0
+ self.column = 0
+
+ self.stream = stream
+ self.name = getattr(stream, 'name', '<file>')
+ self.eof = False
+ self.raw_buffer = None
+
+ while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2):
+ self.update_raw()
+ self.update(1)
+
+ def peek(self, index=0):
+ try:
+ return self.buffer[self.pointer + index]
+ except IndexError:
+ self.update(index + 1)
+ return self.buffer[self.pointer + index]
+
+ def prefix(self, length=1):
+ if self.pointer + length >= len(self.buffer):
+ self.update(length)
+ return self.buffer[self.pointer:self.pointer + length]
+
+ def update_pointer(self, length):
+ while length:
+ ch = self.buffer[self.pointer]
+ self.pointer += 1
+ self.full_pointer += 1
+ self.index += 1
+ if ch == '\n':
+ self.line += 1
+ self.column = 0
+ else:
+ self.column += 1
+ length -= 1
+
+ def forward(self, length=1):
+ if self.pointer + length + 1 >= len(self.buffer):
+ self.update(length + 1)
+ self.update_pointer(length)
+
+ def get_mark(self):
+ return Mark(self.name, self.line, self.column, self.full_buffer, self.full_pointer)
+
+ def check_printable(self, data):
+ match = NON_PRINTABLE_RE.search(data)
+ if match:
+ self.update_pointer(match.start())
+ raise ReaderError(
+ 'while reading from stream', None,
+ 'found special characters which are not allowed',
+ Mark(self.name, self.line, self.column, self.full_buffer, self.full_pointer)
+ )
+
+ def update(self, length):
+ if self.raw_buffer is None:
+ return
+ self.buffer = self.buffer[self.pointer:]
+ self.pointer = 0
+ while len(self.buffer) < length:
+ if not self.eof:
+ self.update_raw()
+ try:
+ data, converted = self.raw_decode(self.raw_buffer, 'strict', self.eof)
+ except UnicodeDecodeError as exc:
+ character = self.raw_buffer[exc.start]
+ position = self.stream_pointer - len(self.raw_buffer) + exc.start
+ data, converted = self.raw_decode(self.raw_buffer[:exc.start], 'strict', self.eof)
+ self.buffer += data
+ self.full_buffer += data + '<' + str(ord(character)) + '>'
+ self.raw_buffer = self.raw_buffer[converted:]
+ self.update_pointer(exc.start - 1)
+ raise ReaderError(
+ 'while reading from stream', None,
+ 'found character #x%04x that cannot be decoded by UTF-8 codec' % ord(character),
+ Mark(self.name, self.line, self.column, self.full_buffer, position)
+ )
+ self.buffer += data
+ self.full_buffer += data
+ self.raw_buffer = self.raw_buffer[converted:]
+ self.check_printable(data)
+ if self.eof:
+ self.buffer += '\0'
+ self.raw_buffer = None
+ break
+
+ def update_raw(self, size=-1):
+ # Was size=4096
+ assert(size < 0)
+ # WARNING: reading the whole stream at once. To change this behaviour to
+ # former reading N characters at once one must make sure that reading
+ # never ends at partial unicode character.
+ data = self.stream.read(size)
+ if self.raw_buffer is None:
+ self.raw_buffer = data
+ else:
+ self.raw_buffer += data
+ self.stream_pointer += len(data)
+ if not data:
+ self.eof = True
diff --git a/powerline/lint/markedjson/resolver.py b/powerline/lint/markedjson/resolver.py
new file mode 100644
index 0000000..fa8ceaa
--- /dev/null
+++ b/powerline/lint/markedjson/resolver.py
@@ -0,0 +1,131 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import re
+
+from powerline.lint.markedjson.error import MarkedError
+from powerline.lint.markedjson import nodes
+
+
+class ResolverError(MarkedError):
+ pass
+
+
+class BaseResolver:
+ DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
+ DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq'
+ DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map'
+
+ yaml_implicit_resolvers = {}
+ yaml_path_resolvers = {}
+
+ def __init__(self):
+ self.resolver_exact_paths = []
+ self.resolver_prefix_paths = []
+
+ @classmethod
+ def add_implicit_resolver(cls, tag, regexp, first):
+ if 'yaml_implicit_resolvers' not in cls.__dict__:
+ cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy()
+ if first is None:
+ first = [None]
+ for ch in first:
+ cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
+
+ def descend_resolver(self, current_node, current_index):
+ if not self.yaml_path_resolvers:
+ return
+ exact_paths = {}
+ prefix_paths = []
+ if current_node:
+ depth = len(self.resolver_prefix_paths)
+ for path, kind in self.resolver_prefix_paths[-1]:
+ if self.check_resolver_prefix(depth, path, kind, current_node, current_index):
+ if len(path) > depth:
+ prefix_paths.append((path, kind))
+ else:
+ exact_paths[kind] = self.yaml_path_resolvers[path, kind]
+ else:
+ for path, kind in self.yaml_path_resolvers:
+ if not path:
+ exact_paths[kind] = self.yaml_path_resolvers[path, kind]
+ else:
+ prefix_paths.append((path, kind))
+ self.resolver_exact_paths.append(exact_paths)
+ self.resolver_prefix_paths.append(prefix_paths)
+
+ def ascend_resolver(self):
+ if not self.yaml_path_resolvers:
+ return
+ self.resolver_exact_paths.pop()
+ self.resolver_prefix_paths.pop()
+
+ def check_resolver_prefix(self, depth, path, kind, current_node, current_index):
+ node_check, index_check = path[depth - 1]
+ if isinstance(node_check, str):
+ if current_node.tag != node_check:
+ return
+ elif node_check is not None:
+ if not isinstance(current_node, node_check):
+ return
+ if index_check is True and current_index is not None:
+ return
+ if ((index_check is False or index_check is None)
+ and current_index is None):
+ return
+ if isinstance(index_check, str):
+ if not (isinstance(current_index, nodes.ScalarNode) and index_check == current_index.value):
+ return
+ elif isinstance(index_check, int) and not isinstance(index_check, bool):
+ if index_check != current_index:
+ return
+ return True
+
+ def resolve(self, kind, value, implicit, mark=None):
+ if kind is nodes.ScalarNode and implicit[0]:
+ if value == '':
+ resolvers = self.yaml_implicit_resolvers.get('', [])
+ else:
+ resolvers = self.yaml_implicit_resolvers.get(value[0], [])
+ resolvers += self.yaml_implicit_resolvers.get(None, [])
+ for tag, regexp in resolvers:
+ if regexp.match(value):
+ return tag
+ else:
+ self.echoerr(
+ 'While resolving plain scalar', None,
+ 'expected floating-point value, integer, null or boolean, but got %r' % value,
+ mark
+ )
+ return self.DEFAULT_SCALAR_TAG
+ if kind is nodes.ScalarNode:
+ return self.DEFAULT_SCALAR_TAG
+ elif kind is nodes.SequenceNode:
+ return self.DEFAULT_SEQUENCE_TAG
+ elif kind is nodes.MappingNode:
+ return self.DEFAULT_MAPPING_TAG
+
+
+class Resolver(BaseResolver):
+ pass
+
+
+Resolver.add_implicit_resolver(
+ 'tag:yaml.org,2002:bool',
+ re.compile(r'''^(?:true|false)$''', re.X),
+ list('yYnNtTfFoO'))
+
+Resolver.add_implicit_resolver(
+ 'tag:yaml.org,2002:float',
+ re.compile(r'^-?(?:0|[1-9]\d*)(?=[.eE])(?:\.\d+)?(?:[eE][-+]?\d+)?$', re.X),
+ list('-0123456789'))
+
+Resolver.add_implicit_resolver(
+ 'tag:yaml.org,2002:int',
+ re.compile(r'^(?:0|-?[1-9]\d*)$', re.X),
+ list('-0123456789'))
+
+Resolver.add_implicit_resolver(
+ 'tag:yaml.org,2002:null',
+ re.compile(r'^null$', re.X),
+ ['n'])
diff --git a/powerline/lint/markedjson/scanner.py b/powerline/lint/markedjson/scanner.py
new file mode 100644
index 0000000..b0bddf3
--- /dev/null
+++ b/powerline/lint/markedjson/scanner.py
@@ -0,0 +1,499 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from string import hexdigits
+
+from powerline.lint.markedjson.error import MarkedError
+from powerline.lint.markedjson import tokens
+from powerline.lib.unicode import unicode, unichr, surrogate_pair_to_character
+
+
+hexdigits_set = set(hexdigits)
+
+
+# Scanner produces tokens of the following types:
+# STREAM-START
+# STREAM-END
+# DOCUMENT-START
+# DOCUMENT-END
+# FLOW-SEQUENCE-START
+# FLOW-MAPPING-START
+# FLOW-SEQUENCE-END
+# FLOW-MAPPING-END
+# FLOW-ENTRY
+# KEY
+# VALUE
+# SCALAR(value, plain, style)
+#
+# Read comments in the Scanner code for more details.
+
+
+class ScannerError(MarkedError):
+ pass
+
+
+class SimpleKey:
+ # See below simple keys treatment.
+ def __init__(self, token_number, index, line, column, mark):
+ self.token_number = token_number
+ self.index = index
+ self.line = line
+ self.column = column
+ self.mark = mark
+
+
+class Scanner:
+ def __init__(self):
+ '''Initialize the scanner.'''
+ # It is assumed that Scanner and Reader will have a common descendant.
+ # Reader do the dirty work of checking for BOM and converting the
+ # input data to Unicode. It also adds NUL to the end.
+ #
+ # Reader supports the following methods
+ # self.peek(i=0) # peek the next i-th character
+ # self.prefix(l=1) # peek the next l characters
+ # self.forward(l=1) # read the next l characters and move the pointer.
+
+ # Had we reached the end of the stream?
+ self.done = False
+
+ # The number of unclosed '{' and '['. `flow_level == 0` means block
+ # context.
+ self.flow_level = 0
+
+ # List of processed tokens that are not yet emitted.
+ self.tokens = []
+
+ # Add the STREAM-START token.
+ self.fetch_stream_start()
+
+ # Number of tokens that were emitted through the `get_token` method.
+ self.tokens_taken = 0
+
+ # Variables related to simple keys treatment.
+
+ # A simple key is a key that is not denoted by the '?' indicator.
+ # We emit the KEY token before all keys, so when we find a potential
+ # simple key, we try to locate the corresponding ':' indicator.
+ # Simple keys should be limited to a single line.
+
+ # Can a simple key start at the current position? A simple key may
+ # start:
+ # - after '{', '[', ',' (in the flow context),
+ self.allow_simple_key = False
+
+ # Keep track of possible simple keys. This is a dictionary. The key
+ # is `flow_level`; there can be no more that one possible simple key
+ # for each level. The value is a SimpleKey record:
+ # (token_number, index, line, column, mark)
+ # A simple key may start with SCALAR(flow), '[', or '{' tokens.
+ self.possible_simple_keys = {}
+
+ # Public methods.
+
+ def check_token(self, *choices):
+ # Check if the next token is one of the given types.
+ while self.need_more_tokens():
+ self.fetch_more_tokens()
+ if self.tokens:
+ if not choices:
+ return True
+ for choice in choices:
+ if isinstance(self.tokens[0], choice):
+ return True
+ return False
+
+ def peek_token(self):
+ # Return the next token, but do not delete if from the queue.
+ while self.need_more_tokens():
+ self.fetch_more_tokens()
+ if self.tokens:
+ return self.tokens[0]
+
+ def get_token(self):
+ # Return the next token.
+ while self.need_more_tokens():
+ self.fetch_more_tokens()
+ if self.tokens:
+ self.tokens_taken += 1
+ return self.tokens.pop(0)
+
+ # Private methods.
+
+ def need_more_tokens(self):
+ if self.done:
+ return False
+ if not self.tokens:
+ return True
+ # The current token may be a potential simple key, so we
+ # need to look further.
+ self.stale_possible_simple_keys()
+ if self.next_possible_simple_key() == self.tokens_taken:
+ return True
+
+ def fetch_more_tokens(self):
+
+ # Eat whitespaces and comments until we reach the next token.
+ self.scan_to_next_token()
+
+ # Remove obsolete possible simple keys.
+ self.stale_possible_simple_keys()
+
+ # Peek the next character.
+ ch = self.peek()
+
+ # Is it the end of stream?
+ if ch == '\0':
+ return self.fetch_stream_end()
+
+ # Note: the order of the following checks is NOT significant.
+
+ # Is it the flow sequence start indicator?
+ if ch == '[':
+ return self.fetch_flow_sequence_start()
+
+ # Is it the flow mapping start indicator?
+ if ch == '{':
+ return self.fetch_flow_mapping_start()
+
+ # Is it the flow sequence end indicator?
+ if ch == ']':
+ return self.fetch_flow_sequence_end()
+
+ # Is it the flow mapping end indicator?
+ if ch == '}':
+ return self.fetch_flow_mapping_end()
+
+ # Is it the flow entry indicator?
+ if ch == ',':
+ return self.fetch_flow_entry()
+
+ # Is it the value indicator?
+ if ch == ':' and self.flow_level:
+ return self.fetch_value()
+
+ # Is it a double quoted scalar?
+ if ch == '"':
+ return self.fetch_double()
+
+ # It must be a plain scalar then.
+ if self.check_plain():
+ return self.fetch_plain()
+
+ # No? It’s an error. Let’s produce a nice error message.
+ raise ScannerError(
+ 'while scanning for the next token', None,
+ 'found character %r that cannot start any token' % ch,
+ self.get_mark()
+ )
+
+ # Simple keys treatment.
+
+ def next_possible_simple_key(self):
+ # Return the number of the nearest possible simple key. Actually we
+ # don’t need to loop through the whole dictionary. We may replace it
+ # with the following code:
+ # if not self.possible_simple_keys:
+ # return None
+ # return self.possible_simple_keys[
+ # min(self.possible_simple_keys.keys())].token_number
+ min_token_number = None
+ for level in self.possible_simple_keys:
+ key = self.possible_simple_keys[level]
+ if min_token_number is None or key.token_number < min_token_number:
+ min_token_number = key.token_number
+ return min_token_number
+
+ def stale_possible_simple_keys(self):
+ # Remove entries that are no longer possible simple keys. According to
+ # the YAML specification, simple keys
+ # - should be limited to a single line,
+ # Disabling this procedure will allow simple keys of any length and
+ # height (may cause problems if indentation is broken though).
+ for level in list(self.possible_simple_keys):
+ key = self.possible_simple_keys[level]
+ if key.line != self.line:
+ del self.possible_simple_keys[level]
+
+ def save_possible_simple_key(self):
+ # The next token may start a simple key. We check if it’s possible
+ # and save its position. This function is called for
+ # SCALAR(flow), '[', and '{'.
+
+ # The next token might be a simple key. Let’s save it’s number and
+ # position.
+ if self.allow_simple_key:
+ self.remove_possible_simple_key()
+ token_number = self.tokens_taken + len(self.tokens)
+ key = SimpleKey(token_number, self.index, self.line, self.column, self.get_mark())
+ self.possible_simple_keys[self.flow_level] = key
+
+ def remove_possible_simple_key(self):
+ # Remove the saved possible key position at the current flow level.
+ if self.flow_level in self.possible_simple_keys:
+ del self.possible_simple_keys[self.flow_level]
+
+ # Fetchers.
+
+ def fetch_stream_start(self):
+ # We always add STREAM-START as the first token and STREAM-END as the
+ # last token.
+
+ # Read the token.
+ mark = self.get_mark()
+
+ # Add STREAM-START.
+ self.tokens.append(tokens.StreamStartToken(mark, mark, encoding=self.encoding))
+
+ def fetch_stream_end(self):
+ # Reset simple keys.
+ self.remove_possible_simple_key()
+ self.allow_simple_key = False
+ self.possible_simple_keys = {}
+
+ # Read the token.
+ mark = self.get_mark()
+
+ # Add STREAM-END.
+ self.tokens.append(tokens.StreamEndToken(mark, mark))
+
+ # The steam is finished.
+ self.done = True
+
+ def fetch_flow_sequence_start(self):
+ self.fetch_flow_collection_start(tokens.FlowSequenceStartToken)
+
+ def fetch_flow_mapping_start(self):
+ self.fetch_flow_collection_start(tokens.FlowMappingStartToken)
+
+ def fetch_flow_collection_start(self, TokenClass):
+ # '[' and '{' may start a simple key.
+ self.save_possible_simple_key()
+
+ # Increase the flow level.
+ self.flow_level += 1
+
+ # Simple keys are allowed after '[' and '{'.
+ self.allow_simple_key = True
+
+ # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(TokenClass(start_mark, end_mark))
+
+ def fetch_flow_sequence_end(self):
+ self.fetch_flow_collection_end(tokens.FlowSequenceEndToken)
+
+ def fetch_flow_mapping_end(self):
+ self.fetch_flow_collection_end(tokens.FlowMappingEndToken)
+
+ def fetch_flow_collection_end(self, TokenClass):
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Decrease the flow level.
+ self.flow_level -= 1
+
+ # No simple keys after ']' or '}'.
+ self.allow_simple_key = False
+
+ # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(TokenClass(start_mark, end_mark))
+
+ def fetch_value(self):
+ # Do we determine a simple key?
+ if self.flow_level in self.possible_simple_keys:
+
+ # Add KEY.
+ key = self.possible_simple_keys[self.flow_level]
+ del self.possible_simple_keys[self.flow_level]
+ self.tokens.insert(key.token_number - self.tokens_taken, tokens.KeyToken(key.mark, key.mark))
+
+ # There cannot be two simple keys one after another.
+ self.allow_simple_key = False
+
+ # Add VALUE.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(tokens.ValueToken(start_mark, end_mark))
+
+ def fetch_flow_entry(self):
+ # Simple keys are allowed after ','.
+ self.allow_simple_key = True
+
+ # Reset possible simple key on the current level.
+ self.remove_possible_simple_key()
+
+ # Add FLOW-ENTRY.
+ start_mark = self.get_mark()
+ self.forward()
+ end_mark = self.get_mark()
+ self.tokens.append(tokens.FlowEntryToken(start_mark, end_mark))
+
+ def fetch_double(self):
+ # A flow scalar could be a simple key.
+ self.save_possible_simple_key()
+
+ # No simple keys after flow scalars.
+ self.allow_simple_key = False
+
+ # Scan and add SCALAR.
+ self.tokens.append(self.scan_flow_scalar())
+
+ def fetch_plain(self):
+
+ self.save_possible_simple_key()
+
+ # No simple keys after plain scalars.
+ self.allow_simple_key = False
+
+ # Scan and add SCALAR. May change `allow_simple_key`.
+ self.tokens.append(self.scan_plain())
+
+ # Checkers.
+
+ def check_plain(self):
+ return self.peek() in '0123456789-ntf'
+
+ # Scanners.
+
+ def scan_to_next_token(self):
+ while self.peek() in ' \t\n':
+ self.forward()
+
+ def scan_flow_scalar(self):
+ # See the specification for details.
+ # Note that we loose indentation rules for quoted scalars. Quoted
+ # scalars don’t need to adhere indentation because " and ' clearly
+ # mark the beginning and the end of them. Therefore we are less
+ # restrictive then the specification requires. We only need to check
+ # that document separators are not included in scalars.
+ chunks = []
+ start_mark = self.get_mark()
+ quote = self.peek()
+ self.forward()
+ chunks.extend(self.scan_flow_scalar_non_spaces(start_mark))
+ while self.peek() != quote:
+ chunks.extend(self.scan_flow_scalar_spaces(start_mark))
+ chunks.extend(self.scan_flow_scalar_non_spaces(start_mark))
+ self.forward()
+ end_mark = self.get_mark()
+ return tokens.ScalarToken(unicode().join(chunks), False, start_mark, end_mark, '"')
+
+ ESCAPE_REPLACEMENTS = {
+ 'b': '\x08',
+ 't': '\x09',
+ 'n': '\x0A',
+ 'f': '\x0C',
+ 'r': '\x0D',
+ '"': '\"',
+ '\\': '\\',
+ }
+
+ ESCAPE_CODES = {
+ 'u': 4,
+ }
+
+ def scan_flow_scalar_non_spaces(self, start_mark):
+ # See the specification for details.
+ chunks = []
+ while True:
+ length = 0
+ while self.peek(length) not in '\"\\\0 \t\n':
+ length += 1
+ if length:
+ chunks.append(self.prefix(length))
+ self.forward(length)
+ ch = self.peek()
+ if ch == '\\':
+ self.forward()
+ ch = self.peek()
+ if ch in self.ESCAPE_REPLACEMENTS:
+ chunks.append(self.ESCAPE_REPLACEMENTS[ch])
+ self.forward()
+ elif ch in self.ESCAPE_CODES:
+ length = self.ESCAPE_CODES[ch]
+ self.forward()
+ for k in range(length):
+ if self.peek(k) not in hexdigits:
+ raise ScannerError(
+ 'while scanning a double-quoted scalar', start_mark,
+ 'expected escape sequence of %d hexdecimal numbers, but found %r' % (
+ length, self.peek(k)),
+ self.get_mark()
+ )
+ code = int(self.prefix(length), 16)
+ self.forward(length)
+ if 0xD800 <= code <= 0xDC00:
+ # Start of the surrogate pair
+ next_char = self.prefix(6)
+ if (
+ next_char[0] != '\\'
+ or next_char[1] != 'u'
+ or not (set(next_char[2:]) < hexdigits_set)
+ or not (0xDC00 <= int(next_char[2:], 16) <= 0xDFFF)
+ ):
+ raise ScannerError(
+ 'while scanning a double-quoted scalar', start_mark,
+ 'expected escape sequence with the next character in surrogate pair, but found %r' % (
+ next_char
+ ),
+ self.get_mark()
+ )
+ code = surrogate_pair_to_character(code, int(next_char[2:], 16))
+ self.forward(6)
+ chunks.append(unichr(code))
+ else:
+ raise ScannerError(
+ 'while scanning a double-quoted scalar', start_mark,
+ ('found unknown escape character %r' % ch), self.get_mark()
+ )
+ else:
+ return chunks
+
+ def scan_flow_scalar_spaces(self, start_mark):
+ # See the specification for details.
+ chunks = []
+ length = 0
+ while self.peek(length) in ' \t':
+ length += 1
+ whitespaces = self.prefix(length)
+ self.forward(length)
+ ch = self.peek()
+ if ch == '\0':
+ raise ScannerError(
+ 'while scanning a quoted scalar', start_mark,
+ 'found unexpected end of stream', self.get_mark()
+ )
+ elif ch == '\n':
+ raise ScannerError(
+ 'while scanning a quoted scalar', start_mark,
+ 'found unexpected line end', self.get_mark()
+ )
+ else:
+ chunks.append(whitespaces)
+ return chunks
+
+ def scan_plain(self):
+ chunks = []
+ start_mark = self.get_mark()
+ spaces = []
+ while True:
+ length = 0
+ while True:
+ if self.peek(length) not in 'eE.0123456789nul-tr+fas':
+ break
+ length += 1
+ if length == 0:
+ break
+ self.allow_simple_key = False
+ chunks.extend(spaces)
+ chunks.append(self.prefix(length))
+ self.forward(length)
+ end_mark = self.get_mark()
+ return tokens.ScalarToken(''.join(chunks), True, start_mark, end_mark)
diff --git a/powerline/lint/markedjson/tokens.py b/powerline/lint/markedjson/tokens.py
new file mode 100644
index 0000000..6fa8bf1
--- /dev/null
+++ b/powerline/lint/markedjson/tokens.py
@@ -0,0 +1,72 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+
+class Token(object):
+ def __init__(self, start_mark, end_mark):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+
+ def __repr__(self):
+ attributes = [
+ key for key in self.__dict__
+ if not key.endswith('_mark')
+ ]
+ attributes.sort()
+ arguments = ', '.join([
+ '%s=%r' % (key, getattr(self, key))
+ for key in attributes
+ ])
+ return '%s(%s)' % (self.__class__.__name__, arguments)
+
+
+class StreamStartToken(Token):
+ id = '<stream start>'
+
+ def __init__(self, start_mark=None, end_mark=None, encoding=None):
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.encoding = encoding
+
+
+class StreamEndToken(Token):
+ id = '<stream end>'
+
+
+class FlowSequenceStartToken(Token):
+ id = '['
+
+
+class FlowMappingStartToken(Token):
+ id = '{'
+
+
+class FlowSequenceEndToken(Token):
+ id = ']'
+
+
+class FlowMappingEndToken(Token):
+ id = '}'
+
+
+class KeyToken(Token):
+ id = '?'
+
+
+class ValueToken(Token):
+ id = ':'
+
+
+class FlowEntryToken(Token):
+ id = ','
+
+
+class ScalarToken(Token):
+ id = '<scalar>'
+
+ def __init__(self, value, plain, start_mark, end_mark, style=None):
+ self.value = value
+ self.plain = plain
+ self.start_mark = start_mark
+ self.end_mark = end_mark
+ self.style = style
diff --git a/powerline/lint/selfcheck.py b/powerline/lint/selfcheck.py
new file mode 100644
index 0000000..06d1fbe
--- /dev/null
+++ b/powerline/lint/selfcheck.py
@@ -0,0 +1,16 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lib.unicode import unicode
+
+
+def havemarks(*args, **kwargs):
+ origin = kwargs.get('origin', '')
+ for i, v in enumerate(args):
+ if not hasattr(v, 'mark'):
+ raise AssertionError('Value #{0}/{1} ({2!r}) has no attribute `mark`'.format(origin, i, v))
+ if isinstance(v, dict):
+ for key, val in v.items():
+ havemarks(key, val, origin=(origin + '[' + unicode(i) + ']/' + unicode(key)))
+ elif isinstance(v, list):
+ havemarks(*v, origin=(origin + '[' + unicode(i) + ']'))
diff --git a/powerline/lint/spec.py b/powerline/lint/spec.py
new file mode 100644
index 0000000..6a54441
--- /dev/null
+++ b/powerline/lint/spec.py
@@ -0,0 +1,759 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import itertools
+import re
+
+from copy import copy
+
+from powerline.lib.unicode import unicode
+from powerline.lint.markedjson.error import echoerr, DelayedEchoErr, NON_PRINTABLE_STR
+from powerline.lint.selfcheck import havemarks
+
+
+NON_PRINTABLE_RE = re.compile(
+ NON_PRINTABLE_STR.translate({
+ ord('\t'): None,
+ ord('\n'): None,
+ 0x0085: None,
+ })
+)
+
+
+class Spec(object):
+ '''Class that describes some JSON value
+
+ In powerline it is only used to describe JSON values stored in powerline
+ configuration.
+
+ :param dict keys:
+ Dictionary that maps keys that may be present in the given JSON
+ dictionary to their descriptions. If this parameter is not empty it
+ implies that described value has dictionary type. Non-dictionary types
+ must be described using ``Spec()``: without arguments.
+
+ .. note::
+ Methods that create the specifications return ``self``, so calls to them
+ may be chained: ``Spec().type(unicode).re('^\w+$')``. This does not
+ apply to functions that *apply* specification like :py:meth`Spec.match`.
+
+ .. note::
+ Methods starting with ``check_`` return two values: first determines
+ whether caller should proceed on running other checks, second
+ determines whether there were any problems (i.e. whether error was
+ reported). One should not call these methods directly: there is
+ :py:meth:`Spec.match` method for checking values.
+
+ .. note::
+ In ``check_`` and ``match`` methods specifications are identified by
+ their indexes for the purpose of simplyfying :py:meth:`Spec.copy`
+ method.
+
+ Some common parameters:
+
+ ``data``:
+ Whatever data supplied by the first caller for checker functions. Is not
+ processed by :py:class:`Spec` methods in any fashion.
+ ``context``:
+ :py:class:`powerline.lint.context.Context` instance, describes context
+ of the value. :py:class:`Spec` methods only use its ``.key`` methods for
+ error messages.
+ ``echoerr``:
+ Callable that should be used to echo errors. Is supposed to take four
+ optional keyword arguments: ``problem``, ``problem_mark``, ``context``,
+ ``context_mark``.
+ ``value``:
+ Checked value.
+ '''
+
+ def __init__(self, **keys):
+ self.specs = []
+ self.keys = {}
+ self.checks = []
+ self.cmsg = ''
+ self.isoptional = False
+ self.uspecs = []
+ self.ufailmsg = lambda key: 'found unknown key: {0}'.format(key)
+ self.did_type = False
+ self.update(**keys)
+
+ def update(self, **keys):
+ '''Describe additional keys that may be present in given JSON value
+
+ If called with some keyword arguments implies that described value is
+ a dictionary. If called without keyword parameters it is no-op.
+
+ :return: self.
+ '''
+ for k, v in keys.items():
+ self.keys[k] = len(self.specs)
+ self.specs.append(v)
+ if self.keys and not self.did_type:
+ self.type(dict)
+ self.did_type = True
+ return self
+
+ def copy(self, copied=None):
+ '''Deep copy the spec
+
+ :param dict copied:
+ Internal dictionary used for storing already copied values. This
+ parameter should not be used.
+
+ :return: New :py:class:`Spec` object that is a deep copy of ``self``.
+ '''
+ copied = copied or {}
+ try:
+ return copied[id(self)]
+ except KeyError:
+ instance = self.__class__()
+ copied[id(self)] = instance
+ return self.__class__()._update(self.__dict__, copied)
+
+ def _update(self, d, copied):
+ '''Helper for the :py:meth:`Spec.copy` function
+
+ Populates new instance with values taken from the old one.
+
+ :param dict d:
+ ``__dict__`` of the old instance.
+ :param dict copied:
+ Storage for already copied values.
+ '''
+ self.__dict__.update(d)
+ self.keys = copy(self.keys)
+ self.checks = copy(self.checks)
+ self.uspecs = copy(self.uspecs)
+ self.specs = [spec.copy(copied) for spec in self.specs]
+ return self
+
+ def unknown_spec(self, keyfunc, spec):
+ '''Define specification for non-static keys
+
+ This method should be used if key names cannot be determined at runtime
+ or if a number of keys share identical spec (in order to not repeat it).
+ :py:meth:`Spec.match` method processes dictionary in the given order:
+
+ * First it tries to use specifications provided at the initialization or
+ by the :py:meth:`Spec.update` method.
+ * If no specification for given key was provided it processes
+ specifications from ``keyfunc`` argument in order they were supplied.
+ Once some key matches specification supplied second ``spec`` argument
+ is used to determine correctness of the value.
+
+ :param Spec keyfunc:
+ :py:class:`Spec` instance or a regular function that returns two
+ values (the same :py:meth:`Spec.match` returns). This argument is
+ used to match keys that were not provided at initialization or via
+ :py:meth:`Spec.update`.
+ :param Spec spec:
+ :py:class:`Spec` instance that will be used to check keys matched by
+ ``keyfunc``.
+
+ :return: self.
+ '''
+ if isinstance(keyfunc, Spec):
+ self.specs.append(keyfunc)
+ keyfunc = len(self.specs) - 1
+ self.specs.append(spec)
+ self.uspecs.append((keyfunc, len(self.specs) - 1))
+ return self
+
+ def unknown_msg(self, msgfunc):
+ '''Define message which will be used when unknown key was found
+
+ “Unknown” is a key that was not provided at the initialization and via
+ :py:meth:`Spec.update` and did not match any ``keyfunc`` provided via
+ :py:meth:`Spec.unknown_spec`.
+
+ :param msgfunc:
+ Function that takes that unknown key as an argument and returns the
+ message text. Text will appear at the top (start of the sentence).
+
+ :return: self.
+ '''
+ self.ufailmsg = msgfunc
+ return self
+
+ def context_message(self, msg):
+ '''Define message that describes context
+
+ :param str msg:
+ Message that describes context. Is written using the
+ :py:meth:`str.format` syntax and is expected to display keyword
+ parameter ``key``.
+
+ :return: self.
+ '''
+ self.cmsg = msg
+ for spec in self.specs:
+ if not spec.cmsg:
+ spec.context_message(msg)
+ return self
+
+ def check_type(self, value, context_mark, data, context, echoerr, types):
+ '''Check that given value matches given type(s)
+
+ :param tuple types:
+ List of accepted types. Since :py:class:`Spec` is supposed to
+ describe JSON values only ``dict``, ``list``, ``unicode``, ``bool``,
+ ``float`` and ``NoneType`` types make any sense.
+
+ :return: proceed, hadproblem.
+ '''
+ havemarks(value)
+ if type(value.value) not in types:
+ echoerr(
+ context=self.cmsg.format(key=context.key),
+ context_mark=context_mark,
+ problem='{0!r} must be a {1} instance, not {2}'.format(
+ value,
+ ', '.join((t.__name__ for t in types)),
+ type(value.value).__name__
+ ),
+ problem_mark=value.mark
+ )
+ return False, True
+ return True, False
+
+ def check_func(self, value, context_mark, data, context, echoerr, func, msg_func):
+ '''Check value using given function
+
+ :param function func:
+ Callable that should accept four positional parameters:
+
+ #. checked value,
+ #. ``data`` parameter with arbitrary data (supplied by top-level
+ caller),
+ #. current context and
+ #. function used for echoing errors.
+
+ This callable should return three values:
+
+ #. determines whether ``check_func`` caller should proceed
+ calling other checks,
+ #. determines whether ``check_func`` should echo error on its own
+ (it should be set to False if ``func`` echoes error itself) and
+ #. determines whether function has found some errors in the checked
+ value.
+
+ :param function msg_func:
+ Callable that takes checked value as the only positional parameter
+ and returns a string that describes the problem. Only useful for
+ small checker functions since it is ignored when second returned
+ value is false.
+
+ :return: proceed, hadproblem.
+ '''
+ havemarks(value)
+ proceed, echo, hadproblem = func(value, data, context, echoerr)
+ if echo and hadproblem:
+ echoerr(context=self.cmsg.format(key=context.key),
+ context_mark=context_mark,
+ problem=msg_func(value),
+ problem_mark=value.mark)
+ return proceed, hadproblem
+
+ def check_list(self, value, context_mark, data, context, echoerr, item_func, msg_func):
+ '''Check that each value in the list matches given specification
+
+ :param function item_func:
+ Callable like ``func`` from :py:meth:`Spec.check_func`. Unlike
+ ``func`` this callable is called for each value in the list and may
+ be a :py:class:`Spec` object index.
+ :param func msg_func:
+ Callable like ``msg_func`` from :py:meth:`Spec.check_func`. Should
+ accept one problematic item and is not used for :py:class:`Spec`
+ object indices in ``item_func`` method.
+
+ :return: proceed, hadproblem.
+ '''
+ havemarks(value)
+ i = 0
+ hadproblem = False
+ for item in value:
+ havemarks(item)
+ if isinstance(item_func, int):
+ spec = self.specs[item_func]
+ proceed, fhadproblem = spec.match(
+ item,
+ value.mark,
+ data,
+ context.enter_item('list item ' + unicode(i), item),
+ echoerr
+ )
+ else:
+ proceed, echo, fhadproblem = item_func(item, data, context, echoerr)
+ if echo and fhadproblem:
+ echoerr(context=self.cmsg.format(key=context.key + '/list item ' + unicode(i)),
+ context_mark=value.mark,
+ problem=msg_func(item),
+ problem_mark=item.mark)
+ if fhadproblem:
+ hadproblem = True
+ if not proceed:
+ return proceed, hadproblem
+ i += 1
+ return True, hadproblem
+
+ def check_either(self, value, context_mark, data, context, echoerr, start, end):
+ '''Check that given value matches one of the given specifications
+
+ :param int start:
+ First specification index.
+ :param int end:
+ Specification index that is greater by 1 then last specification
+ index.
+
+ This method does not give an error if any specification from
+ ``self.specs[start:end]`` is matched by the given value.
+ '''
+ havemarks(value)
+ new_echoerr = DelayedEchoErr(
+ echoerr,
+ 'One of the either variants failed. Messages from the first variant:',
+ 'messages from the next variant:'
+ )
+
+ hadproblem = False
+ for spec in self.specs[start:end]:
+ proceed, hadproblem = spec.match(value, value.mark, data, context, new_echoerr)
+ new_echoerr.next_variant()
+ if not proceed:
+ break
+ if not hadproblem:
+ return True, False
+
+ new_echoerr.echo_all()
+
+ return False, hadproblem
+
+ def check_tuple(self, value, context_mark, data, context, echoerr, start, end):
+ '''Check that given value is a list with items matching specifications
+
+ :param int start:
+ First specification index.
+ :param int end:
+ Specification index that is greater by 1 then last specification
+ index.
+
+ This method checks that each item in the value list matches
+ specification with index ``start + item_number``.
+ '''
+ havemarks(value)
+ hadproblem = False
+ for (i, item, spec) in zip(itertools.count(), value, self.specs[start:end]):
+ proceed, ihadproblem = spec.match(
+ item,
+ value.mark,
+ data,
+ context.enter_item('tuple item ' + unicode(i), item),
+ echoerr
+ )
+ if ihadproblem:
+ hadproblem = True
+ if not proceed:
+ return False, hadproblem
+ return True, hadproblem
+
+ def check_printable(self, value, context_mark, data, context, echoerr, _):
+ '''Check that given unicode string contains only printable characters
+ '''
+ hadproblem = False
+ for match in NON_PRINTABLE_RE.finditer(value):
+ hadproblem = True
+ echoerr(
+ context=self.cmsg.format(key=context.key),
+ context_mark=value.mark,
+ problem='found not printable character U+{0:04x} in a configuration string'.format(
+ ord(match.group(0))),
+ problem_mark=value.mark.advance_string(match.start() + 1)
+ )
+ return True, hadproblem
+
+ def printable(self, *args):
+ self.type(unicode)
+ self.checks.append(('check_printable', args))
+ return self
+
+ def type(self, *args):
+ '''Describe value that has one of the types given in arguments
+
+ :param args:
+ List of accepted types. Since :py:class:`Spec` is supposed to
+ describe JSON values only ``dict``, ``list``, ``unicode``, ``bool``,
+ ``float`` and ``NoneType`` types make any sense.
+
+ :return: self.
+ '''
+ self.checks.append(('check_type', args))
+ return self
+
+ cmp_funcs = {
+ 'le': lambda x, y: x <= y,
+ 'lt': lambda x, y: x < y,
+ 'ge': lambda x, y: x >= y,
+ 'gt': lambda x, y: x > y,
+ 'eq': lambda x, y: x == y,
+ }
+
+ cmp_msgs = {
+ 'le': 'lesser or equal to',
+ 'lt': 'lesser then',
+ 'ge': 'greater or equal to',
+ 'gt': 'greater then',
+ 'eq': 'equal to',
+ }
+
+ def len(self, comparison, cint, msg_func=None):
+ '''Describe value that has given length
+
+ :param str comparison:
+ Type of the comparison. Valid values: ``le``, ``lt``, ``ge``,
+ ``gt``, ``eq``.
+ :param int cint:
+ Integer with which length is compared.
+ :param function msg_func:
+ Function that should accept checked value and return message that
+ describes the problem with this value. Default value will emit
+ something like “length of ['foo', 'bar'] is not greater then 10”.
+
+ :return: self.
+ '''
+ cmp_func = self.cmp_funcs[comparison]
+ msg_func = (
+ msg_func
+ or (lambda value: 'length of {0!r} is not {1} {2}'.format(
+ value, self.cmp_msgs[comparison], cint))
+ )
+ self.checks.append((
+ 'check_func',
+ (lambda value, *args: (True, True, not cmp_func(len(value), cint))),
+ msg_func
+ ))
+ return self
+
+ def cmp(self, comparison, cint, msg_func=None):
+ '''Describe value that is a number or string that has given property
+
+ :param str comparison:
+ Type of the comparison. Valid values: ``le``, ``lt``, ``ge``,
+ ``gt``, ``eq``. This argument will restrict the number or string to
+ emit True on the given comparison.
+ :param cint:
+ Number or string with which value is compared. Type of this
+ parameter affects required type of the checked value: ``str`` and
+ ``unicode`` types imply ``unicode`` values, ``float`` type implies
+ that value can be either ``int`` or ``float``, ``int`` type implies
+ ``int`` value and for any other type the behavior is undefined.
+ :param function msg_func:
+ Function that should accept checked value and return message that
+ describes the problem with this value. Default value will emit
+ something like “10 is not greater then 10”.
+
+ :return: self.
+ '''
+ if type(cint) is str:
+ self.type(unicode)
+ elif type(cint) is float:
+ self.type(int, float)
+ else:
+ self.type(type(cint))
+ cmp_func = self.cmp_funcs[comparison]
+ msg_func = msg_func or (lambda value: '{0} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint))
+ self.checks.append((
+ 'check_func',
+ (lambda value, *args: (True, True, not cmp_func(value.value, cint))),
+ msg_func
+ ))
+ return self
+
+ def unsigned(self, msg_func=None):
+ '''Describe unsigned integer value
+
+ :param function msg_func:
+ Function that should accept checked value and return message that
+ describes the problem with this value.
+
+ :return: self.
+ '''
+ self.type(int)
+ self.checks.append((
+ 'check_func',
+ (lambda value, *args: (True, True, value < 0)),
+ (lambda value: '{0} must be greater then zero'.format(value))
+ ))
+ return self
+
+ def list(self, item_func, msg_func=None):
+ '''Describe list with any number of elements, each matching given spec
+
+ :param item_func:
+ :py:class:`Spec` instance or a callable. Check out
+ :py:meth:`Spec.check_list` documentation for more details. Note that
+ in :py:meth:`Spec.check_list` description :py:class:`Spec` instance
+ is replaced with its index in ``self.specs``.
+ :param function msg_func:
+ Function that should accept checked value and return message that
+ describes the problem with this value. Default value will emit just
+ “failed check”, which is rather indescriptive.
+
+ :return: self.
+ '''
+ self.type(list)
+ if isinstance(item_func, Spec):
+ self.specs.append(item_func)
+ item_func = len(self.specs) - 1
+ self.checks.append(('check_list', item_func, msg_func or (lambda item: 'failed check')))
+ return self
+
+ def tuple(self, *specs):
+ '''Describe list with the given number of elements, each matching corresponding spec
+
+ :param (Spec,) specs:
+ List of specifications. Last element(s) in this list may be
+ optional. Each element in this list describes element with the same
+ index in the checked value. Check out :py:meth:`Spec.check_tuple`
+ for more details, but note that there list of specifications is
+ replaced with start and end indices in ``self.specs``.
+
+ :return: self.
+ '''
+ self.type(list)
+
+ max_len = len(specs)
+ min_len = max_len
+ for spec in reversed(specs):
+ if spec.isoptional:
+ min_len -= 1
+ else:
+ break
+ if max_len == min_len:
+ self.len('eq', len(specs))
+ else:
+ if min_len > 0:
+ self.len('ge', min_len)
+ self.len('le', max_len)
+
+ start = len(self.specs)
+ for i, spec in zip(itertools.count(), specs):
+ self.specs.append(spec)
+ self.checks.append(('check_tuple', start, len(self.specs)))
+ return self
+
+ def func(self, func, msg_func=None):
+ '''Describe value that is checked by the given function
+
+ Check out :py:meth:`Spec.check_func` documentation for more details.
+ '''
+ self.checks.append(('check_func', func, msg_func or (lambda value: 'failed check')))
+ return self
+
+ def re(self, regex, msg_func=None):
+ '''Describe value that is a string that matches given regular expression
+
+ :param str regex:
+ Regular expression that should be matched by the value.
+ :param function msg_func:
+ Function that should accept checked value and return message that
+ describes the problem with this value. Default value will emit
+ something like “String "xyz" does not match "[a-f]+"”.
+
+ :return: self.
+ '''
+ self.type(unicode)
+ compiled = re.compile(regex)
+ msg_func = msg_func or (lambda value: 'String "{0}" does not match "{1}"'.format(value, regex))
+ self.checks.append((
+ 'check_func',
+ (lambda value, *args: (True, True, not compiled.match(value.value))),
+ msg_func
+ ))
+ return self
+
+ def ident(self, msg_func=None):
+ '''Describe value that is an identifier like ``foo:bar`` or ``foo``
+
+ :param function msg_func:
+ Function that should accept checked value and return message that
+ describes the problem with this value. Default value will emit
+ something like “String "xyz" is not an … identifier”.
+
+ :return: self.
+ '''
+ msg_func = (
+ msg_func
+ or (lambda value: 'String "{0}" is not an alphanumeric/underscore colon-separated identifier'.format(value))
+ )
+ return self.re('^\w+(?::\w+)?$', msg_func)
+
+ def oneof(self, collection, msg_func=None):
+ '''Describe value that is equal to one of the value in the collection
+
+ :param set collection:
+ A collection of possible values.
+ :param function msg_func:
+ Function that should accept checked value and return message that
+ describes the problem with this value. Default value will emit
+ something like “"xyz" must be one of {'abc', 'def', 'ghi'}”.
+
+ :return: self.
+ '''
+ msg_func = msg_func or (lambda value: '"{0}" must be one of {1!r}'.format(value, list(collection)))
+ self.checks.append((
+ 'check_func',
+ (lambda value, *args: (True, True, value not in collection)),
+ msg_func
+ ))
+ return self
+
+ def error(self, msg):
+ '''Describe value that must not be there
+
+ Useful for giving more descriptive errors for some specific keys then
+ just “found unknown key: shutdown_event” or for forbidding certain
+ values when :py:meth:`Spec.unknown_spec` was used.
+
+ :param str msg:
+ Message given for the offending value. It is formatted using
+ :py:meth:`str.format` with the only positional parameter which is
+ the value itself.
+
+ :return: self.
+ '''
+ self.checks.append((
+ 'check_func',
+ (lambda *args: (True, True, True)),
+ (lambda value: msg.format(value))
+ ))
+ return self
+
+ def either(self, *specs):
+ '''Describes value that matches one of the given specs
+
+ Check out :py:meth:`Spec.check_either` method documentation for more
+ details, but note that there a list of specs was replaced by start and
+ end indices in ``self.specs``.
+
+ :return: self.
+ '''
+ start = len(self.specs)
+ self.specs.extend(specs)
+ self.checks.append(('check_either', start, len(self.specs)))
+ return self
+
+ def optional(self):
+ '''Mark value as optional
+
+ Only useful for key specs in :py:meth:`Spec.__init__` and
+ :py:meth:`Spec.update` and some last supplied to :py:meth:`Spec.tuple`.
+
+ :return: self.
+ '''
+ self.isoptional = True
+ return self
+
+ def required(self):
+ '''Mark value as required
+
+ Only useful for key specs in :py:meth:`Spec.__init__` and
+ :py:meth:`Spec.update` and some last supplied to :py:meth:`Spec.tuple`.
+
+ .. note::
+ Value is required by default. This method is only useful for
+ altering existing specification (or rather its copy).
+
+ :return: self.
+ '''
+ self.isoptional = False
+ return self
+
+ def match_checks(self, *args):
+ '''Process checks registered for the given value
+
+ Processes only “top-level” checks: key specifications given using at the
+ initialization or via :py:meth:`Spec.unknown_spec` are processed by
+ :py:meth:`Spec.match`.
+
+ :return: proceed, hadproblem.
+ '''
+ hadproblem = False
+ for check in self.checks:
+ proceed, chadproblem = getattr(self, check[0])(*(args + check[1:]))
+ if chadproblem:
+ hadproblem = True
+ if not proceed:
+ return False, hadproblem
+ return True, hadproblem
+
+ def match(self, value, context_mark=None, data=None, context=(), echoerr=echoerr):
+ '''Check that given value matches this specification
+
+ :return: proceed, hadproblem.
+ '''
+ havemarks(value)
+ proceed, hadproblem = self.match_checks(value, context_mark, data, context, echoerr)
+ if proceed:
+ if self.keys or self.uspecs:
+ for key, vali in self.keys.items():
+ valspec = self.specs[vali]
+ if key in value:
+ proceed, mhadproblem = valspec.match(
+ value[key],
+ value.mark,
+ data,
+ context.enter_key(value, key),
+ echoerr
+ )
+ if mhadproblem:
+ hadproblem = True
+ if not proceed:
+ return False, hadproblem
+ else:
+ if not valspec.isoptional:
+ hadproblem = True
+ echoerr(context=self.cmsg.format(key=context.key),
+ context_mark=None,
+ problem='required key is missing: {0}'.format(key),
+ problem_mark=value.mark)
+ for key in value.keys():
+ havemarks(key)
+ if key not in self.keys:
+ for keyfunc, vali in self.uspecs:
+ valspec = self.specs[vali]
+ if isinstance(keyfunc, int):
+ spec = self.specs[keyfunc]
+ proceed, khadproblem = spec.match(key, context_mark, data, context, echoerr)
+ else:
+ proceed, khadproblem = keyfunc(key, data, context, echoerr)
+ if khadproblem:
+ hadproblem = True
+ if proceed:
+ proceed, vhadproblem = valspec.match(
+ value[key],
+ value.mark,
+ data,
+ context.enter_key(value, key),
+ echoerr
+ )
+ if vhadproblem:
+ hadproblem = True
+ break
+ else:
+ hadproblem = True
+ if self.ufailmsg:
+ echoerr(context=self.cmsg.format(key=context.key),
+ context_mark=None,
+ problem=self.ufailmsg(key),
+ problem_mark=key.mark)
+ return True, hadproblem
+
+ def __getitem__(self, key):
+ '''Get specification for the given key
+ '''
+ return self.specs[self.keys[key]]
+
+ def __setitem__(self, key, value):
+ '''Set specification for the given key
+ '''
+ self.update(**{key: value})
diff --git a/powerline/listers/__init__.py b/powerline/listers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/listers/__init__.py
diff --git a/powerline/listers/i3wm.py b/powerline/listers/i3wm.py
new file mode 100644
index 0000000..9224d16
--- /dev/null
+++ b/powerline/listers/i3wm.py
@@ -0,0 +1,63 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.theme import requires_segment_info
+from powerline.lib.dict import updated
+from powerline.bindings.wm import get_i3_connection, get_connected_xrandr_outputs
+
+
+@requires_segment_info
+def output_lister(pl, segment_info):
+ '''List all outputs in segment_info format
+ '''
+
+ return (
+ (
+ updated(segment_info, output=output['name']),
+ {
+ 'draw_inner_divider': None
+ }
+ )
+ for output in get_connected_xrandr_outputs(pl)
+ )
+
+
+@requires_segment_info
+def workspace_lister(pl, segment_info, only_show=None, output=None):
+ '''List all workspaces in segment_info format
+
+ Sets the segment info values of ``workspace`` and ``output`` to the name of
+ the i3 workspace and the ``xrandr`` output respectively and the keys
+ ``"visible"``, ``"urgent"`` and ``"focused"`` to a boolean indicating these
+ states.
+
+ :param list only_show:
+ Specifies which workspaces to list. Valid entries are ``"visible"``,
+ ``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces
+ are listed.
+
+ :param str output:
+ May be set to the name of an X output. If specified, only workspaces
+ on that output are listed. Overrides automatic output detection by
+ the lemonbar renderer and bindings. Set to ``false`` to force
+ all workspaces to be shown.
+ '''
+
+ if output == None:
+ output = output or segment_info.get('output')
+
+ return (
+ (
+ updated(
+ segment_info,
+ output=w.output,
+ workspace=w,
+ ),
+ {
+ 'draw_inner_divider': None
+ }
+ )
+ for w in get_i3_connection().get_workspaces()
+ if (((not only_show or any(getattr(w, typ) for typ in only_show))
+ and (not output or w.output == output)))
+ )
diff --git a/powerline/listers/pdb.py b/powerline/listers/pdb.py
new file mode 100644
index 0000000..24e11ea
--- /dev/null
+++ b/powerline/listers/pdb.py
@@ -0,0 +1,37 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.theme import requires_segment_info
+
+
+@requires_segment_info
+def frame_lister(pl, segment_info, full_stack=False, maxframes=3):
+ '''List all frames in segment_info format
+
+ :param bool full_stack:
+ If true, then all frames in the stack are listed. Normally N first
+ frames are discarded where N is a number of frames present at the first
+ invocation of the prompt minus one.
+ :param int maxframes:
+ Maximum number of frames to display.
+ '''
+ if full_stack:
+ initial_stack_length = 0
+ frames = segment_info['pdb'].stack
+ else:
+ initial_stack_length = segment_info['initial_stack_length']
+ frames = segment_info['pdb'].stack[initial_stack_length:]
+
+ if len(frames) > maxframes:
+ frames = frames[-maxframes:]
+
+ return (
+ (
+ {
+ 'curframe': frame[0],
+ 'initial_stack_length': initial_stack_length,
+ },
+ {}
+ )
+ for frame in frames
+ )
diff --git a/powerline/listers/vim.py b/powerline/listers/vim.py
new file mode 100644
index 0000000..583e0d3
--- /dev/null
+++ b/powerline/listers/vim.py
@@ -0,0 +1,123 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.theme import requires_segment_info
+from powerline.bindings.vim import (current_tabpage, list_tabpages)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+
+def tabpage_updated_segment_info(segment_info, tabpage):
+ segment_info = segment_info.copy()
+ window = tabpage.window
+ buffer = window.buffer
+ segment_info.update(
+ tabpage=tabpage,
+ tabnr=tabpage.number,
+ window=window,
+ winnr=window.number,
+ window_id=int(window.vars.get('powerline_window_id', -1)),
+ buffer=buffer,
+ bufnr=buffer.number,
+ )
+ return segment_info
+
+
+@requires_segment_info
+def tablister(pl, segment_info, **kwargs):
+ '''List all tab pages in segment_info format
+
+ Specifically generates a list of segment info dictionaries with ``window``,
+ ``winnr``, ``window_id``, ``buffer`` and ``bufnr`` keys set to tab-local
+ ones and additional ``tabpage`` and ``tabnr`` keys.
+
+ Adds either ``tab:`` or ``tab_nc:`` prefix to all segment highlight groups.
+
+ Works best with vim-7.4 or later: earlier versions miss tabpage object and
+ thus window objects are not available as well.
+ '''
+ cur_tabpage = current_tabpage()
+ cur_tabnr = cur_tabpage.number
+
+ def add_multiplier(tabpage, dct):
+ dct['priority_multiplier'] = 1 + (0.001 * abs(tabpage.number - cur_tabnr))
+ return dct
+
+ return (
+ (lambda tabpage, prefix: (
+ tabpage_updated_segment_info(segment_info, tabpage),
+ add_multiplier(tabpage, {
+ 'highlight_group_prefix': prefix,
+ 'divider_highlight_group': 'tab:divider'
+ })
+ ))(tabpage, 'tab' if tabpage == cur_tabpage else 'tab_nc')
+ for tabpage in list_tabpages()
+ )
+
+
+def buffer_updated_segment_info(segment_info, buffer):
+ segment_info = segment_info.copy()
+ segment_info.update(
+ window=None,
+ winnr=None,
+ window_id=None,
+ buffer=buffer,
+ bufnr=buffer.number,
+ )
+ return segment_info
+
+
+@requires_segment_info
+def bufferlister(pl, segment_info, show_unlisted=False, **kwargs):
+ '''List all buffers in segment_info format
+
+ Specifically generates a list of segment info dictionaries with ``buffer``
+ and ``bufnr`` keys set to buffer-specific ones, ``window``, ``winnr`` and
+ ``window_id`` keys set to None.
+
+ Adds one of ``buf:``, ``buf_nc:``, ``buf_mod:``, or ``buf_nc_mod``
+ prefix to all segment highlight groups.
+
+ :param bool show_unlisted:
+ True if unlisted buffers should be shown as well. Current buffer is
+ always shown.
+ '''
+ cur_buffer = vim.current.buffer
+ cur_bufnr = cur_buffer.number
+
+ def add_multiplier(buffer, dct):
+ dct['priority_multiplier'] = 1 + (0.001 * abs(buffer.number - cur_bufnr))
+ return dct
+
+ return (
+ (lambda buffer, current, modified: (
+ buffer_updated_segment_info(segment_info, buffer),
+ add_multiplier(buffer, {
+ 'highlight_group_prefix': '{0}{1}'.format(current, modified),
+ 'divider_highlight_group': 'tab:divider'
+ })
+ ))(
+ buffer,
+ 'buf' if buffer is cur_buffer else 'buf_nc',
+ '_mod' if int(vim.eval('getbufvar({0}, \'&modified\')'.format(buffer.number))) > 0 else ''
+ )
+ for buffer in vim.buffers if (
+ buffer is cur_buffer
+ or show_unlisted
+ # We can't use vim_getbufoption(segment_info, 'buflisted')
+ # here for performance reasons. Querying the buffer options
+ # through the vim python module's option attribute caused
+ # vim to think it needed to update the tabline for every
+ # keystroke after any event that changed the buffer's
+ # options.
+ #
+ # Using the vim module's eval method to directly use the
+ # buflisted(nr) vim method instead does not cause vim to
+ # update the tabline after every keystroke, but rather after
+ # events that would change that status. Fixes #1281
+ or int(vim.eval('buflisted(%s)' % buffer.number)) > 0
+ )
+ )
diff --git a/powerline/matchers/__init__.py b/powerline/matchers/__init__.py
new file mode 100644
index 0000000..b2b9f10
--- /dev/null
+++ b/powerline/matchers/__init__.py
@@ -0,0 +1,6 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+from pkgutil import extend_path
+
+
+__path__ = extend_path(__path__, __name__)
diff --git a/powerline/matchers/vim/__init__.py b/powerline/matchers/vim/__init__.py
new file mode 100644
index 0000000..f6de45e
--- /dev/null
+++ b/powerline/matchers/vim/__init__.py
@@ -0,0 +1,19 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from powerline.bindings.vim import vim_getbufoption, buffer_name
+
+
+def help(matcher_info):
+ return vim_getbufoption(matcher_info, 'buftype') == 'help'
+
+
+def cmdwin(matcher_info):
+ name = buffer_name(matcher_info)
+ return name and os.path.basename(name) == b'[Command Line]'
+
+
+def quickfix(matcher_info):
+ return vim_getbufoption(matcher_info, 'buftype') == 'quickfix'
diff --git a/powerline/matchers/vim/plugin/__init__.py b/powerline/matchers/vim/plugin/__init__.py
new file mode 100644
index 0000000..b2b9f10
--- /dev/null
+++ b/powerline/matchers/vim/plugin/__init__.py
@@ -0,0 +1,6 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+from pkgutil import extend_path
+
+
+__path__ = extend_path(__path__, __name__)
diff --git a/powerline/matchers/vim/plugin/commandt.py b/powerline/matchers/vim/plugin/commandt.py
new file mode 100644
index 0000000..7eefe9b
--- /dev/null
+++ b/powerline/matchers/vim/plugin/commandt.py
@@ -0,0 +1,14 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from powerline.bindings.vim import vim_getbufoption, buffer_name
+
+
+def commandt(matcher_info):
+ name = buffer_name(matcher_info)
+ return (
+ vim_getbufoption(matcher_info, 'filetype') == 'command-t'
+ or (name and os.path.basename(name) == b'GoToFile')
+ )
diff --git a/powerline/matchers/vim/plugin/gundo.py b/powerline/matchers/vim/plugin/gundo.py
new file mode 100644
index 0000000..e0fe377
--- /dev/null
+++ b/powerline/matchers/vim/plugin/gundo.py
@@ -0,0 +1,16 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from powerline.bindings.vim import buffer_name
+
+
+def gundo(matcher_info):
+ name = buffer_name(matcher_info)
+ return name and os.path.basename(name) == b'__Gundo__'
+
+
+def gundo_preview(matcher_info):
+ name = buffer_name(matcher_info)
+ return name and os.path.basename(name) == b'__Gundo_Preview__'
diff --git a/powerline/matchers/vim/plugin/nerdtree.py b/powerline/matchers/vim/plugin/nerdtree.py
new file mode 100644
index 0000000..d6e9f69
--- /dev/null
+++ b/powerline/matchers/vim/plugin/nerdtree.py
@@ -0,0 +1,15 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import re
+
+from powerline.bindings.vim import buffer_name
+
+
+NERD_TREE_RE = re.compile(b'NERD_tree_\\d+')
+
+
+def nerdtree(matcher_info):
+ name = buffer_name(matcher_info)
+ return name and NERD_TREE_RE.match(os.path.basename(name))
diff --git a/powerline/pdb.py b/powerline/pdb.py
new file mode 100644
index 0000000..b1e13ce
--- /dev/null
+++ b/powerline/pdb.py
@@ -0,0 +1,48 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import platform
+import os
+
+from powerline import Powerline
+from powerline.lib.overrides import parse_override_var
+from powerline.lib.dict import mergeargs, mergedicts
+
+
+class PDBPowerline(Powerline):
+ '''PDB-specific powerline bindings
+ '''
+ def init(self, **kwargs):
+ return super(PDBPowerline, self).init(
+ ext='pdb',
+ renderer_module='pdb',
+ **kwargs
+ )
+
+ def do_setup(self, pdb):
+ self.update_renderer()
+ self.renderer.set_pdb(pdb)
+
+ def load_main_config(self):
+ r = super(PDBPowerline, self).load_main_config()
+ config_overrides = os.environ.get('POWERLINE_CONFIG_OVERRIDES')
+ if config_overrides:
+ mergedicts(r, mergeargs(parse_override_var(config_overrides)))
+ return r
+
+ def load_theme_config(self, name):
+ r = super(PDBPowerline, self).load_theme_config(name)
+ theme_overrides = os.environ.get('POWERLINE_THEME_OVERRIDES')
+ if theme_overrides:
+ theme_overrides_dict = mergeargs(parse_override_var(theme_overrides))
+ if name in theme_overrides_dict:
+ mergedicts(r, theme_overrides_dict[name])
+ return r
+
+ def get_config_paths(self):
+ paths = [path for path in os.environ.get('POWERLINE_CONFIG_PATHS', '').split(':') if path]
+ return paths or super(PDBPowerline, self).get_config_paths()
+
+ if sys.version_info < (3,) and platform.python_implementation() == 'PyPy':
+ get_encoding = staticmethod(lambda: 'ascii')
diff --git a/powerline/renderer.py b/powerline/renderer.py
new file mode 100644
index 0000000..31aca80
--- /dev/null
+++ b/powerline/renderer.py
@@ -0,0 +1,606 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import os
+import re
+import operator
+
+from itertools import chain
+
+from powerline.theme import Theme
+from powerline.lib.unicode import unichr, strwidth_ucs_2, strwidth_ucs_4
+
+
+NBSP = ' '
+
+
+np_control_character_translations = dict((
+ # Control characters: ^@ … ^Y
+ (i1, '^' + unichr(i1 + 0x40)) for i1 in range(0x20)
+))
+'''Control character translations
+
+Dictionary that maps characters in range 0x00–0x1F (inclusive) to strings
+``'^@'``, ``'^A'`` and so on.
+
+.. note: maps tab to ``^I`` and newline to ``^J``.
+'''
+
+np_invalid_character_translations = dict((
+ # Invalid unicode characters obtained using 'surrogateescape' error
+ # handler.
+ (i2, '<{0:02x}>'.format(i2 - 0xDC00)) for i2 in range(0xDC80, 0xDD00)
+))
+'''Invalid unicode character translations
+
+When using ``surrogateescape`` encoding error handling method characters in
+range 0x80–0xFF (inclusive) are transformed into unpaired surrogate escape
+unicode codepoints 0xDC80–0xDD00. This dictionary maps such characters to
+``<80>``, ``<81>``, and so on: in Python-3 they cannot be printed or
+converted to UTF-8 because UTF-8 standard does not allow surrogate escape
+characters, not even paired ones. Python-2 contains a bug that allows such
+action, but printing them in any case makes no sense.
+'''
+
+# XXX: not using `r` because it makes no sense.
+np_invalid_character_re = re.compile('(?<![\uD800-\uDBFF])[\uDC80-\uDD00]')
+'''Regex that finds unpaired surrogate escape characters
+
+Search is only limited to the ones obtained from ``surrogateescape`` error
+handling method. This regex is only used for UCS-2 Python variants because
+in this case characters above 0xFFFF are represented as surrogate escapes
+characters and are thus subject to partial transformation if
+``np_invalid_character_translations`` translation table is used.
+'''
+
+np_character_translations = np_control_character_translations.copy()
+'''Dictionary that contains non-printable character translations
+
+In UCS-4 versions of Python this is a union of
+``np_invalid_character_translations`` and ``np_control_character_translations``
+dictionaries. In UCS-2 for technical reasons ``np_invalid_character_re`` is used
+instead and this dictionary only contains items from
+``np_control_character_translations``.
+'''
+
+translate_np = (
+ (
+ lambda s: (
+ np_invalid_character_re.subn(
+ lambda match: (
+ np_invalid_character_translations[ord(match.group(0))]
+ ), s
+ )[0].translate(np_character_translations)
+ )
+ ) if sys.maxunicode < 0x10FFFF else (
+ lambda s: (
+ s.translate(np_character_translations)
+ )
+ )
+)
+'''Function that translates non-printable characters into printable strings
+
+Is used to translate control characters and surrogate escape characters
+obtained from ``surrogateescape`` encoding errors handling method into some
+printable sequences. See documentation for
+``np_invalid_character_translations`` and
+``np_control_character_translations`` for more details.
+'''
+
+
+def construct_returned_value(rendered_highlighted, segments, width, output_raw, output_width):
+ if not (output_raw or output_width):
+ return rendered_highlighted
+ else:
+ return (
+ (rendered_highlighted,)
+ + ((''.join((segment['_rendered_raw'] for segment in segments)),) if output_raw else ())
+ + ((width,) if output_width else ())
+ )
+
+
+class Renderer(object):
+ '''Object that is responsible for generating the highlighted string.
+
+ :param dict theme_config:
+ Main theme configuration.
+ :param local_themes:
+ Local themes. Is to be used by subclasses from ``.get_theme()`` method,
+ base class only records this parameter to a ``.local_themes`` attribute.
+ :param dict theme_kwargs:
+ Keyword arguments for ``Theme`` class constructor.
+ :param PowerlineLogger pl:
+ Object used for logging.
+ :param int ambiwidth:
+ Width of the characters with east asian width unicode attribute equal to
+ ``A`` (Ambiguous).
+ :param dict options:
+ Various options. Are normally not used by base renderer, but all options
+ are recorded as attributes.
+ '''
+
+ segment_info = {
+ 'environ': os.environ,
+ 'getcwd': getattr(os, 'getcwdu', os.getcwd),
+ 'home': os.environ.get('HOME'),
+ }
+ '''Basic segment info
+
+ Is merged with local segment information by :py:meth:`get_segment_info`
+ method. Keys:
+
+ ``environ``
+ Object containing environment variables. Must define at least the
+ following methods: ``.__getitem__(var)`` that raises ``KeyError`` in
+ case requested environment variable is not present, ``.get(var,
+ default=None)`` that works like ``dict.get`` and be able to be passed to
+ ``Popen``.
+
+ ``getcwd``
+ Function that returns current working directory. Will be called without
+ any arguments, should return ``unicode`` or (in python-2) regular
+ string.
+
+ ``home``
+ String containing path to home directory. Should be ``unicode`` or (in
+ python-2) regular string or ``None``.
+ '''
+
+ character_translations = {}
+ '''Character translations for use in escape() function.
+
+ See documentation of ``unicode.translate`` for details.
+ '''
+
+ def __init__(self,
+ theme_config,
+ local_themes,
+ theme_kwargs,
+ pl,
+ ambiwidth=1,
+ **options):
+ self.__dict__.update(options)
+ self.theme_config = theme_config
+ theme_kwargs['pl'] = pl
+ self.pl = pl
+ if theme_config.get('use_non_breaking_spaces', True):
+ self.character_translations = self.character_translations.copy()
+ self.character_translations[ord(' ')] = NBSP
+ self.theme = Theme(theme_config=theme_config, **theme_kwargs)
+ self.local_themes = local_themes
+ self.theme_kwargs = theme_kwargs
+ self.width_data = {
+ 'N': 1, # Neutral
+ 'Na': 1, # Narrow
+ 'A': ambiwidth, # Ambiguous
+ 'H': 1, # Half-width
+ 'W': 2, # Wide
+ 'F': 2, # Fullwidth
+ }
+
+ strwidth = lambda self, s: (
+ (strwidth_ucs_2 if sys.maxunicode < 0x10FFFF else strwidth_ucs_4)(
+ self.width_data, s)
+ )
+ '''Function that returns string width.
+
+ Is used to calculate the place given string occupies when handling
+ ``width`` argument to ``.render()`` method. Must take east asian width
+ into account.
+
+ :param unicode string:
+ String whose width will be calculated.
+
+ :return: unsigned integer.
+ '''
+
+ def get_theme(self, matcher_info):
+ '''Get Theme object.
+
+ Is to be overridden by subclasses to support local themes, this variant
+ only returns ``.theme`` attribute.
+
+ :param matcher_info:
+ Parameter ``matcher_info`` that ``.render()`` method received.
+ Unused.
+ '''
+ return self.theme
+
+ def shutdown(self):
+ '''Prepare for interpreter shutdown. The only job it is supposed to do
+ is calling ``.shutdown()`` method for all theme objects. Should be
+ overridden by subclasses in case they support local themes.
+ '''
+ self.theme.shutdown()
+
+ def get_segment_info(self, segment_info, mode):
+ '''Get segment information.
+
+ Must return a dictionary containing at least ``home``, ``environ`` and
+ ``getcwd`` keys (see documentation for ``segment_info`` attribute). This
+ implementation merges ``segment_info`` dictionary passed to
+ ``.render()`` method with ``.segment_info`` attribute, preferring keys
+ from the former. It also replaces ``getcwd`` key with function returning
+ ``segment_info['environ']['PWD']`` in case ``PWD`` variable is
+ available.
+
+ :param dict segment_info:
+ Segment information that was passed to ``.render()`` method.
+
+ :return: dict with segment information.
+ '''
+ r = self.segment_info.copy()
+ r['mode'] = mode
+ if segment_info:
+ r.update(segment_info)
+ if 'PWD' in r['environ']:
+ r['getcwd'] = lambda: r['environ']['PWD']
+ return r
+
+ def render_above_lines(self, **kwargs):
+ '''Render all segments in the {theme}/segments/above list
+
+ Rendering happens in the reversed order. Parameters are the same as in
+ .render() method.
+
+ :yield: rendered line.
+ '''
+
+ theme = self.get_theme(kwargs.get('matcher_info', None))
+ for line in range(theme.get_line_number() - 1, 0, -1):
+ yield self.render(side=None, line=line, **kwargs)
+
+ def render(self, mode=None, width=None, side=None, line=0, output_raw=False, output_width=False, segment_info=None, matcher_info=None, hl_args=None):
+ '''Render all segments.
+
+ When a width is provided, low-priority segments are dropped one at
+ a time until the line is shorter than the width, or only segments
+ with a negative priority are left. If one or more segments with
+ ``"width": "auto"`` are provided they will fill the remaining space
+ until the desired width is reached.
+
+ :param str mode:
+ Mode string. Affects contents (colors and the set of segments) of
+ rendered string.
+ :param int width:
+ Maximum width text can occupy. May be exceeded if there are too much
+ non-removable segments.
+ :param str side:
+ One of ``left``, ``right``. Determines which side will be rendered.
+ If not present all sides are rendered.
+ :param int line:
+ Line number for which segments should be obtained. Is counted from
+ zero (botmost line).
+ :param bool output_raw:
+ Changes the output: if this parameter is ``True`` then in place of
+ one string this method outputs a pair ``(colored_string,
+ colorless_string)``.
+ :param bool output_width:
+ Changes the output: if this parameter is ``True`` then in place of
+ one string this method outputs a pair ``(colored_string,
+ string_width)``. Returns a three-tuple if ``output_raw`` is also
+ ``True``: ``(colored_string, colorless_string, string_width)``.
+ :param dict segment_info:
+ Segment information. See also :py:meth:`get_segment_info` method.
+ :param matcher_info:
+ Matcher information. Is processed in :py:meth:`get_segment_info`
+ method.
+ :param dict hl_args:
+ Additional arguments to pass on the :py:meth:`hl` and
+ :py:meth`hlstyle` methods. They are ignored in the default
+ implementation, but renderer-specific overrides can make use of
+ them as run-time "configuration" information.
+ '''
+ theme = self.get_theme(matcher_info)
+ return self.do_render(
+ mode=mode,
+ width=width,
+ side=side,
+ line=line,
+ output_raw=output_raw,
+ output_width=output_width,
+ segment_info=self.get_segment_info(segment_info, mode),
+ theme=theme,
+ hl_args=hl_args
+ )
+
+ def compute_divider_widths(self, theme):
+ return {
+ 'left': {
+ 'hard': self.strwidth(theme.get_divider('left', 'hard')),
+ 'soft': self.strwidth(theme.get_divider('left', 'soft')),
+ },
+ 'right': {
+ 'hard': self.strwidth(theme.get_divider('right', 'hard')),
+ 'soft': self.strwidth(theme.get_divider('right', 'soft')),
+ },
+ }
+
+ hl_join = staticmethod(''.join)
+ '''Join a list of rendered segments into a resulting string
+
+ This method exists to deal with non-string render outputs, so `segments`
+ may actually be not an iterable with strings.
+
+ :param list segments:
+ Iterable containing rendered segments. By “rendered segments”
+ :py:meth:`Renderer.hl` output is meant.
+
+ :return: Results of joining these segments.
+ '''
+
+ def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme, hl_args):
+ '''Like Renderer.render(), but accept theme in place of matcher_info
+ '''
+ segments = list(theme.get_segments(side, line, segment_info, mode))
+
+ current_width = 0
+
+ self._prepare_segments(segments, output_width or width)
+
+ hl_args = hl_args or dict()
+
+ if not width:
+ # No width specified, so we don’t need to crop or pad anything
+ if output_width:
+ current_width = self._render_length(theme, segments, self.compute_divider_widths(theme))
+ return construct_returned_value(self.hl_join([
+ segment['_rendered_hl']
+ for segment in self._render_segments(theme, segments, hl_args)
+ ]) + self.hlstyle(**hl_args), segments, current_width, output_raw, output_width)
+
+ divider_widths = self.compute_divider_widths(theme)
+
+ # Create an ordered list of segments that can be dropped
+ segments_priority = sorted((segment for segment in segments if segment['priority'] is not None), key=lambda segment: segment['priority'], reverse=True)
+ no_priority_segments = filter(lambda segment: segment['priority'] is None, segments)
+ current_width = self._render_length(theme, segments, divider_widths)
+ if current_width > width:
+ for segment in chain(segments_priority, no_priority_segments):
+ if segment['truncate'] is not None:
+ segment['contents'] = segment['truncate'](self.pl, current_width - width, segment)
+
+ segments_priority = iter(segments_priority)
+ if current_width > width and len(segments) > 100:
+ # When there are too many segments use faster, but less correct
+ # algorithm for width computation
+ diff = current_width - width
+ for segment in segments_priority:
+ segments.remove(segment)
+ diff -= segment['_len']
+ if diff <= 0:
+ break
+ current_width = self._render_length(theme, segments, divider_widths)
+ if current_width > width:
+ # When there are not too much use more precise, but much slower
+ # width computation. It also finishes computations in case
+ # previous variant did not free enough space.
+ for segment in segments_priority:
+ segments.remove(segment)
+ current_width = self._render_length(theme, segments, divider_widths)
+ if current_width <= width:
+ break
+ del segments_priority
+
+ # Distribute the remaining space on spacer segments
+ segments_spacers = [segment for segment in segments if segment['expand'] is not None]
+ if segments_spacers:
+ distribute_len, distribute_len_remainder = divmod(width - current_width, len(segments_spacers))
+ for segment in segments_spacers:
+ segment['contents'] = (
+ segment['expand'](
+ self.pl,
+ distribute_len + (1 if distribute_len_remainder > 0 else 0),
+ segment))
+ distribute_len_remainder -= 1
+ # `_len` key is not needed anymore, but current_width should have an
+ # actual value for various bindings.
+ current_width = width
+ elif output_width:
+ current_width = self._render_length(theme, segments, divider_widths)
+
+ rendered_highlighted = self.hl_join([
+ segment['_rendered_hl']
+ for segment in self._render_segments(theme, segments, hl_args)
+ ])
+ if rendered_highlighted:
+ rendered_highlighted += self.hlstyle(**hl_args)
+
+ return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width)
+
+ def _prepare_segments(self, segments, calculate_contents_len):
+ '''Translate non-printable characters and calculate segment width
+ '''
+ for segment in segments:
+ segment['contents'] = translate_np(segment['contents'])
+ if calculate_contents_len:
+ for segment in segments:
+ if segment['literal_contents'][1]:
+ segment['_contents_len'] = segment['literal_contents'][0]
+ else:
+ segment['_contents_len'] = self.strwidth(segment['contents'])
+
+ def _render_length(self, theme, segments, divider_widths):
+ '''Update segments lengths and return them
+ '''
+ segments_len = len(segments)
+ ret = 0
+ divider_spaces = theme.get_spaces()
+ prev_segment = theme.EMPTY_SEGMENT
+ try:
+ first_segment = next(iter((
+ segment
+ for segment in segments
+ if not segment['literal_contents'][1]
+ )))
+ except StopIteration:
+ first_segment = None
+ try:
+ last_segment = next(iter((
+ segment
+ for segment in reversed(segments)
+ if not segment['literal_contents'][1]
+ )))
+ except StopIteration:
+ last_segment = None
+ for index, segment in enumerate(segments):
+ side = segment['side']
+ segment_len = segment['_contents_len']
+ if not segment['literal_contents'][1]:
+ if side == 'left':
+ if segment is not last_segment:
+ compare_segment = next(iter((
+ segment
+ for segment in segments[index + 1:]
+ if not segment['literal_contents'][1]
+ )))
+ else:
+ compare_segment = theme.EMPTY_SEGMENT
+ else:
+ compare_segment = prev_segment
+
+ divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
+
+ outer_padding = int(bool(
+ segment is first_segment
+ if side == 'left' else
+ segment is last_segment
+ )) * theme.outer_padding
+
+ draw_divider = segment['draw_' + divider_type + '_divider']
+ segment_len += outer_padding
+ if draw_divider:
+ segment_len += divider_widths[side][divider_type] + divider_spaces
+ prev_segment = segment
+
+ segment['_len'] = segment_len
+ ret += segment_len
+ return ret
+
+ def _render_segments(self, theme, segments, hl_args, render_highlighted=True):
+ '''Internal segment rendering method.
+
+ This method loops through the segment array and compares the
+ foreground/background colors and divider properties and returns the
+ rendered statusline as a string.
+
+ The method always renders the raw segment contents (i.e. without
+ highlighting strings added), and only renders the highlighted
+ statusline if render_highlighted is True.
+ '''
+ segments_len = len(segments)
+ divider_spaces = theme.get_spaces()
+ prev_segment = theme.EMPTY_SEGMENT
+ try:
+ first_segment = next(iter((
+ segment
+ for segment in segments
+ if not segment['literal_contents'][1]
+ )))
+ except StopIteration:
+ first_segment = None
+ try:
+ last_segment = next(iter((
+ segment
+ for segment in reversed(segments)
+ if not segment['literal_contents'][1]
+ )))
+ except StopIteration:
+ last_segment = None
+
+ for index, segment in enumerate(segments):
+ side = segment['side']
+ if not segment['literal_contents'][1]:
+ if side == 'left':
+ if segment is not last_segment:
+ compare_segment = next(iter((
+ segment
+ for segment in segments[index + 1:]
+ if not segment['literal_contents'][1]
+ )))
+ else:
+ compare_segment = theme.EMPTY_SEGMENT
+ else:
+ compare_segment = prev_segment
+ outer_padding = int(bool(
+ segment is first_segment
+ if side == 'left' else
+ segment is last_segment
+ )) * theme.outer_padding * ' '
+ divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard'
+
+ divider_highlighted = ''
+ contents_raw = segment['contents']
+ contents_highlighted = ''
+ draw_divider = segment['draw_' + divider_type + '_divider']
+
+ segment_hl_args = {}
+ segment_hl_args.update(segment['highlight'])
+ segment_hl_args.update(hl_args)
+
+ # XXX Make sure self.hl() calls are called in the same order
+ # segments are displayed. This is needed for Vim renderer to work.
+ if draw_divider:
+ divider_raw = self.escape(theme.get_divider(side, divider_type))
+ if side == 'left':
+ contents_raw = outer_padding + contents_raw + (divider_spaces * ' ')
+ else:
+ contents_raw = (divider_spaces * ' ') + contents_raw + outer_padding
+
+ if divider_type == 'soft':
+ divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight'
+ divider_fg = segment[divider_highlight_group_key]['fg']
+ divider_bg = segment[divider_highlight_group_key]['bg']
+ else:
+ divider_fg = segment['highlight']['bg']
+ divider_bg = compare_segment['highlight']['bg']
+
+ if side == 'left':
+ if render_highlighted:
+ contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
+ divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
+ segment['_rendered_raw'] = contents_raw + divider_raw
+ segment['_rendered_hl'] = contents_highlighted + divider_highlighted
+ else:
+ if render_highlighted:
+ divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
+ contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
+ segment['_rendered_raw'] = divider_raw + contents_raw
+ segment['_rendered_hl'] = divider_highlighted + contents_highlighted
+ else:
+ if side == 'left':
+ contents_raw = outer_padding + contents_raw
+ else:
+ contents_raw = contents_raw + outer_padding
+
+ contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
+ segment['_rendered_raw'] = contents_raw
+ segment['_rendered_hl'] = contents_highlighted
+ prev_segment = segment
+ else:
+ segment['_rendered_raw'] = ' ' * segment['literal_contents'][0]
+ segment['_rendered_hl'] = segment['literal_contents'][1]
+ yield segment
+
+ def escape(self, string):
+ '''Method that escapes segment contents.
+ '''
+ return string.translate(self.character_translations)
+
+ def hlstyle(fg=None, bg=None, attrs=None, **kwargs):
+ '''Output highlight style string.
+
+ Assuming highlighted string looks like ``{style}{contents}`` this method
+ should output ``{style}``. If it is called without arguments this method
+ is supposed to reset style to its default.
+ '''
+ raise NotImplementedError
+
+ def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
+ '''Output highlighted chunk.
+
+ This implementation just outputs :py:meth:`hlstyle` joined with
+ ``contents``.
+ '''
+ return self.hlstyle(fg, bg, attrs, **kwargs) + (contents or '')
diff --git a/powerline/renderers/__init__.py b/powerline/renderers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/renderers/__init__.py
diff --git a/powerline/renderers/i3bar.py b/powerline/renderers/i3bar.py
new file mode 100644
index 0000000..3eab61f
--- /dev/null
+++ b/powerline/renderers/i3bar.py
@@ -0,0 +1,36 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import json
+
+from powerline.renderer import Renderer
+
+
+class I3barRenderer(Renderer):
+ '''I3bar Segment Renderer.
+
+ Currently works only for i3bgbar (i3 bar with custom patches).
+ '''
+
+ @staticmethod
+ def hlstyle(*args, **kwargs):
+ # We don’t need to explicitly reset attributes, so skip those calls
+ return ''
+
+ def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
+ segment = {
+ 'full_text': contents,
+ 'separator': False,
+ 'separator_block_width': 0, # no separators
+ }
+
+ if fg is not None:
+ if fg is not False and fg[1] is not False:
+ segment['color'] = '#{0:06x}'.format(fg[1])
+ if bg is not None:
+ if bg is not False and bg[1] is not False:
+ segment['background'] = '#{0:06x}'.format(bg[1])
+ return json.dumps(segment) + ','
+
+
+renderer = I3barRenderer
diff --git a/powerline/renderers/ipython/__init__.py b/powerline/renderers/ipython/__init__.py
new file mode 100644
index 0000000..8f463b5
--- /dev/null
+++ b/powerline/renderers/ipython/__init__.py
@@ -0,0 +1,34 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.theme import Theme
+from powerline.renderers.shell import PromptRenderer
+
+
+class IPythonRenderer(PromptRenderer):
+ '''Powerline ipython segment renderer.'''
+ def get_segment_info(self, segment_info, mode):
+ r = self.segment_info.copy()
+ r['ipython'] = segment_info
+ return r
+
+ def get_theme(self, matcher_info):
+ if matcher_info == 'in':
+ return self.theme
+ else:
+ match = self.local_themes[matcher_info]
+ try:
+ return match['theme']
+ except KeyError:
+ match['theme'] = Theme(
+ theme_config=match['config'],
+ main_theme_config=self.theme_config,
+ **self.theme_kwargs
+ )
+ return match['theme']
+
+ def shutdown(self):
+ self.theme.shutdown()
+ for match in self.local_themes.values():
+ if 'theme' in match:
+ match['theme'].shutdown()
diff --git a/powerline/renderers/ipython/pre_5.py b/powerline/renderers/ipython/pre_5.py
new file mode 100644
index 0000000..9fc8c21
--- /dev/null
+++ b/powerline/renderers/ipython/pre_5.py
@@ -0,0 +1,56 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderers.shell import ShellRenderer
+from powerline.renderers.shell.readline import ReadlineRenderer
+from powerline.renderers.ipython import IPythonRenderer
+
+
+class IPythonPre50Renderer(IPythonRenderer, ShellRenderer):
+ '''Powerline ipython segment renderer for pre-5.0 IPython versions.'''
+ def render(self, **kwargs):
+ # XXX super(ShellRenderer), *not* super(IPythonPre50Renderer)
+ return super(ShellRenderer, self).render(**kwargs)
+
+ def do_render(self, segment_info, **kwargs):
+ segment_info.update(client_id='ipython')
+ return super(IPythonPre50Renderer, self).do_render(
+ segment_info=segment_info,
+ **kwargs
+ )
+
+
+class IPythonPromptRenderer(IPythonPre50Renderer, ReadlineRenderer):
+ '''Powerline ipython prompt (in and in2) renderer'''
+ pass
+
+
+class IPythonNonPromptRenderer(IPythonPre50Renderer):
+ '''Powerline ipython non-prompt (out and rewrite) renderer'''
+ pass
+
+
+class RendererProxy(object):
+ '''Powerline IPython renderer proxy which chooses appropriate renderer
+
+ Instantiates two renderer objects: one will be used for prompts and the
+ other for non-prompts.
+ '''
+ def __init__(self, **kwargs):
+ old_widths = {}
+ self.non_prompt_renderer = IPythonNonPromptRenderer(old_widths=old_widths, **kwargs)
+ self.prompt_renderer = IPythonPromptRenderer(old_widths=old_widths, **kwargs)
+
+ def render_above_lines(self, *args, **kwargs):
+ return self.non_prompt_renderer.render_above_lines(*args, **kwargs)
+
+ def render(self, is_prompt, *args, **kwargs):
+ return (self.prompt_renderer if is_prompt else self.non_prompt_renderer).render(
+ *args, **kwargs)
+
+ def shutdown(self, *args, **kwargs):
+ self.prompt_renderer.shutdown(*args, **kwargs)
+ self.non_prompt_renderer.shutdown(*args, **kwargs)
+
+
+renderer = RendererProxy
diff --git a/powerline/renderers/ipython/since_5.py b/powerline/renderers/ipython/since_5.py
new file mode 100644
index 0000000..88c7625
--- /dev/null
+++ b/powerline/renderers/ipython/since_5.py
@@ -0,0 +1,130 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import operator
+
+from collections import defaultdict
+
+try:
+ from __builtin__ import reduce
+except ImportError:
+ from functools import reduce
+
+from pygments.token import Token
+from prompt_toolkit.styles import DynamicStyle, Attrs
+
+from powerline.renderers.ipython import IPythonRenderer
+from powerline.ipython import IPythonInfo
+from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
+
+
+PowerlinePromptToken = Token.Generic.Prompt.Powerline
+
+
+# Note: since 2.7 there is dict.__missing__ with same purpose. But in 2.6 one
+# must use defaultdict to get __missing__ working.
+class PowerlineStyleDict(defaultdict):
+ '''Dictionary used for getting pygments style for Powerline groups
+ '''
+ def __new__(cls, missing_func):
+ return defaultdict.__new__(cls)
+
+ def __init__(self, missing_func):
+ super(PowerlineStyleDict, self).__init__()
+ self.missing_func = missing_func
+
+ def __missing__(self, key):
+ return self.missing_func(key)
+
+
+class PowerlinePromptStyle(DynamicStyle):
+ def get_attrs_for_token(self, token):
+ if (
+ token not in PowerlinePromptToken
+ or len(token) != len(PowerlinePromptToken) + 1
+ or not token[-1].startswith('Pl')
+ or token[-1] == 'Pl'
+ ):
+ return super(PowerlinePromptStyle, self).get_attrs_for_token(token)
+ ret = {
+ 'color': None,
+ 'bgcolor': None,
+ 'bold': None,
+ 'underline': None,
+ 'italic': None,
+ 'reverse': False,
+ 'blink': False,
+ }
+ for prop in token[-1][3:].split('_'):
+ if prop[0] == 'a':
+ ret[prop[1:]] = True
+ elif prop[0] == 'f':
+ ret['color'] = prop[1:]
+ elif prop[0] == 'b':
+ ret['bgcolor'] = prop[1:]
+ return Attrs(**ret)
+
+ def get_token_to_attributes_dict(self):
+ dct = super(PowerlinePromptStyle, self).get_token_to_attributes_dict()
+
+ def fallback(key):
+ try:
+ return dct[key]
+ except KeyError:
+ return self.get_attrs_for_token(key)
+
+ return PowerlineStyleDict(fallback)
+
+ def invalidation_hash(self):
+ return super(PowerlinePromptStyle, self).invalidation_hash() + 1
+
+
+class IPythonPygmentsRenderer(IPythonRenderer):
+ reduce_initial = []
+
+ def get_segment_info(self, segment_info, mode):
+ return super(IPythonPygmentsRenderer, self).get_segment_info(
+ IPythonInfo(segment_info), mode)
+
+ @staticmethod
+ def hl_join(segments):
+ return reduce(operator.iadd, segments, [])
+
+ def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
+ '''Output highlighted chunk.
+
+ This implementation outputs a list containing a single pair
+ (:py:class:`pygments.token.Token`,
+ :py:class:`powerline.lib.unicode.unicode`).
+ '''
+ guifg = None
+ guibg = None
+ attrs = []
+ if fg is not None and fg is not False:
+ guifg = fg[1]
+ if bg is not None and bg is not False:
+ guibg = bg[1]
+ if attrs:
+ attrs = []
+ if attrs & ATTR_BOLD:
+ attrs.append('bold')
+ if attrs & ATTR_ITALIC:
+ attrs.append('italic')
+ if attrs & ATTR_UNDERLINE:
+ attrs.append('underline')
+ name = (
+ 'Pl'
+ + ''.join(('_a' + attr for attr in attrs))
+ + (('_f%6x' % guifg) if guifg is not None else '')
+ + (('_b%6x' % guibg) if guibg is not None else '')
+ )
+ return [(getattr(Token.Generic.Prompt.Powerline, name), contents)]
+
+ def hlstyle(self, **kwargs):
+ return []
+
+ def get_client_id(self, segment_info):
+ return id(self)
+
+
+renderer = IPythonPygmentsRenderer
diff --git a/powerline/renderers/ipython/since_7.py b/powerline/renderers/ipython/since_7.py
new file mode 100644
index 0000000..ca2ea0e
--- /dev/null
+++ b/powerline/renderers/ipython/since_7.py
@@ -0,0 +1,91 @@
+# vim:fileencoding=utf-8:noet
+import operator
+
+try:
+ from __builtin__ import reduce
+except ImportError:
+ from functools import reduce
+
+from pygments.token import Token
+from prompt_toolkit.styles import DynamicStyle
+
+from powerline.renderers.ipython import IPythonRenderer
+from powerline.ipython import IPythonInfo
+from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
+
+used_styles = []
+seen = set()
+
+class PowerlinePromptStyle(DynamicStyle):
+ @property
+ def style_rules(self):
+ return (self.get_style() or self._dummy).style_rules + used_styles
+
+ def invalidation_hash(self):
+ return (h + 1 for h in tuple(super(PowerlinePromptStyle, self).invalidation_hash()))
+
+
+class IPythonPygmentsRenderer(IPythonRenderer):
+ reduce_initial = []
+
+ def __init__(self, **kwargs):
+ super(IPythonPygmentsRenderer, self).__init__(**kwargs)
+ self.character_translations[ord(' ')] = ' '
+
+ def get_segment_info(self, segment_info, mode):
+ return super(IPythonPygmentsRenderer, self).get_segment_info(
+ IPythonInfo(segment_info), mode)
+
+ @staticmethod
+ def hl_join(segments):
+ return reduce(operator.iadd, segments, [])
+
+ def hl(self, escaped_contents, fg=None, bg=None, attrs=None, *args, **kwargs):
+ '''Output highlighted chunk.
+
+ This implementation outputs a list containing a single pair
+ (:py:class:`string`,
+ :py:class:`powerline.lib.unicode.unicode`).
+ '''
+ guifg = None
+ guibg = None
+ att = []
+ if fg is not None and fg is not False:
+ guifg = fg[1]
+ if bg is not None and bg is not False:
+ guibg = bg[1]
+ if attrs:
+ att = []
+ if attrs & ATTR_BOLD:
+ att.append('bold')
+ if attrs & ATTR_ITALIC:
+ att.append('italic')
+ if attrs & ATTR_UNDERLINE:
+ att.append('underline')
+
+ fg = (('%06x' % guifg) if guifg is not None else '')
+ bg = (('%06x' % guibg) if guibg is not None else '')
+ name = (
+ 'pl'
+ + ''.join(('_a' + attr for attr in att))
+ + '_f' + fg + '_b' + bg
+ )
+
+ global seen
+ if not (name in seen):
+ global used_styles
+ used_styles += [('pygments.' + name,
+ ''.join((' ' + attr for attr in att))
+ + (' fg:#' + fg if fg != '' else ' fg:')
+ + (' bg:#' + bg if bg != '' else ' bg:'))]
+ seen.add(name)
+ return [((name,), escaped_contents)]
+
+ def hlstyle(self, *args, **kwargs):
+ return []
+
+ def get_client_id(self, segment_info):
+ return id(self)
+
+
+renderer = IPythonPygmentsRenderer
diff --git a/powerline/renderers/lemonbar.py b/powerline/renderers/lemonbar.py
new file mode 100644
index 0000000..8156807
--- /dev/null
+++ b/powerline/renderers/lemonbar.py
@@ -0,0 +1,61 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderer import Renderer
+from powerline.theme import Theme
+from powerline.colorscheme import ATTR_UNDERLINE
+
+
+class LemonbarRenderer(Renderer):
+ '''lemonbar (formerly bar/bar ain't recursive) renderer
+
+
+ See documentation of `lemonbar <https://github.com/LemonBoy/bar>`_ and :ref:`the usage instructions <lemonbar-usage>`
+ '''
+
+ character_translations = Renderer.character_translations.copy()
+ character_translations[ord('%')] = '%%{}'
+
+ @staticmethod
+ def hlstyle(*args, **kwargs):
+ # We don’t need to explicitly reset attributes, so skip those calls
+ return ''
+
+ def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
+ text = ''
+
+ if fg is not None:
+ if fg is not False and fg[1] is not False:
+ text += '%{{F#ff{0:06x}}}'.format(fg[1])
+ if bg is not None:
+ if bg is not False and bg[1] is not False:
+ text += '%{{B#ff{0:06x}}}'.format(bg[1])
+
+ if attrs & ATTR_UNDERLINE:
+ text += '%{+u}'
+
+ return text + contents + '%{F-B--u}'
+
+ def render(self, *args, **kwargs):
+ return '%{{l}}{0}%{{r}}{1}'.format(
+ super(LemonbarRenderer, self).render(side='left', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs),
+ super(LemonbarRenderer, self).render(side='right', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs),
+ )
+
+ def get_theme(self, matcher_info):
+ if not matcher_info or matcher_info not in self.local_themes:
+ return self.theme
+ match = self.local_themes[matcher_info]
+
+ try:
+ return match['theme']
+ except KeyError:
+ match['theme'] = Theme(
+ theme_config=match['config'],
+ main_theme_config=self.theme_config,
+ **self.theme_kwargs
+ )
+ return match['theme']
+
+
+renderer = LemonbarRenderer
diff --git a/powerline/renderers/pango_markup.py b/powerline/renderers/pango_markup.py
new file mode 100644
index 0000000..3c1a675
--- /dev/null
+++ b/powerline/renderers/pango_markup.py
@@ -0,0 +1,39 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from xml.sax.saxutils import escape as _escape
+
+from powerline.renderer import Renderer
+from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
+
+
+class PangoMarkupRenderer(Renderer):
+ '''Powerline Pango markup segment renderer.'''
+
+ @staticmethod
+ def hlstyle(*args, **kwargs):
+ # We don’t need to explicitly reset attributes, so skip those calls
+ return ''
+
+ def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
+ '''Highlight a segment.'''
+ awesome_attr = []
+ if fg is not None:
+ if fg is not False and fg[1] is not False:
+ awesome_attr += ['foreground="#{0:06x}"'.format(fg[1])]
+ if bg is not None:
+ if bg is not False and bg[1] is not False:
+ awesome_attr += ['background="#{0:06x}"'.format(bg[1])]
+ if attrs is not None and attrs is not False:
+ if attrs & ATTR_BOLD:
+ awesome_attr += ['font_weight="bold"']
+ if attrs & ATTR_ITALIC:
+ awesome_attr += ['font_style="italic"']
+ if attrs & ATTR_UNDERLINE:
+ awesome_attr += ['underline="single"']
+ return '<span ' + ' '.join(awesome_attr) + '>' + contents + '</span>'
+
+ escape = staticmethod(_escape)
+
+
+renderer = PangoMarkupRenderer
diff --git a/powerline/renderers/pdb.py b/powerline/renderers/pdb.py
new file mode 100644
index 0000000..040f0e1
--- /dev/null
+++ b/powerline/renderers/pdb.py
@@ -0,0 +1,50 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import platform
+
+from powerline.renderers.shell.readline import ReadlineRenderer
+from powerline.renderer import Renderer
+
+
+class PDBRenderer(ReadlineRenderer):
+ '''PDB-specific powerline renderer
+ '''
+ pdb = None
+ initial_stack_length = None
+
+ def get_segment_info(self, segment_info, mode):
+ r = self.segment_info.copy()
+ r['pdb'] = self.pdb
+ r['initial_stack_length'] = self.initial_stack_length
+ r['curframe'] = self.pdb.curframe
+ return r
+
+ def set_pdb(self, pdb):
+ '''Record currently used :py:class:`pdb.Pdb` instance
+
+ Must be called before first calling :py:meth:`render` method.
+
+ :param pdb.Pdb pdb:
+ Used :py:class:`pdb.Pdb` instance. This instance will later be used
+ by :py:meth:`get_segment_info` for patching :ref:`segment_info
+ <dev-segments-info>` dictionary.
+ '''
+ self.pdb = pdb
+
+ def render(self, **kwargs):
+ if self.initial_stack_length is None:
+ self.initial_stack_length = len(self.pdb.stack) - 1
+ return Renderer.render(self, **kwargs)
+
+ if sys.version_info < (3,) and platform.python_implementation() == 'PyPy':
+ def do_render(self, **kwargs):
+ # Make sure that only ASCII characters survive
+ ret = super(PDBRenderer, self).do_render(**kwargs)
+ ret = ret.encode('ascii', 'replace')
+ ret = ret.decode('ascii')
+ return ret
+
+
+renderer = PDBRenderer
diff --git a/powerline/renderers/shell/__init__.py b/powerline/renderers/shell/__init__.py
new file mode 100644
index 0000000..d7dbf96
--- /dev/null
+++ b/powerline/renderers/shell/__init__.py
@@ -0,0 +1,182 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderer import Renderer
+from powerline.theme import Theme
+from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
+
+
+def int_to_rgb(num):
+ r = (num >> 16) & 0xff
+ g = (num >> 8) & 0xff
+ b = num & 0xff
+ return r, g, b
+
+
+class PromptRenderer(Renderer):
+ '''Powerline generic prompt segment renderer'''
+
+ def __init__(self, old_widths=None, **kwargs):
+ super(PromptRenderer, self).__init__(**kwargs)
+ self.old_widths = old_widths if old_widths is not None else {}
+
+ def get_client_id(self, segment_info):
+ '''Get client ID given segment info
+
+ This is used by daemon to correctly cache widths for different clients
+ using a single renderer instance.
+
+ :param dict segment_info:
+ :ref:`Segment info dictionary <dev-segments-info>`. Out of it only
+ ``client_id`` key is used. It is OK for this dictionary to not
+ contain this key.
+
+ :return: Any hashable value or ``None``.
+ '''
+ return segment_info.get('client_id') if isinstance(segment_info, dict) else None
+
+ def do_render(self, output_width, segment_info, side, theme, width=None, **kwargs):
+ client_id = self.get_client_id(segment_info)
+ if client_id is not None:
+ local_key = (client_id, side, None if theme is self.theme else id(theme))
+ key = (client_id, side, None)
+ did_width = False
+ if local_key[-1] != key[-1] and side == 'left':
+ try:
+ width = self.old_widths[key]
+ except KeyError:
+ pass
+ else:
+ did_width = True
+ if not did_width and width is not None:
+ if theme.cursor_space_multiplier is not None:
+ width = int(width * theme.cursor_space_multiplier)
+ elif theme.cursor_columns:
+ width -= theme.cursor_columns
+
+ if side == 'right':
+ try:
+ width -= self.old_widths[(client_id, 'left', local_key[-1])]
+ except KeyError:
+ pass
+ res = super(PromptRenderer, self).do_render(
+ output_width=True,
+ width=width,
+ theme=theme,
+ segment_info=segment_info,
+ side=side,
+ **kwargs
+ )
+ if client_id is not None:
+ self.old_widths[local_key] = res[-1]
+ ret = res if output_width else res[:-1]
+ if len(ret) == 1:
+ return ret[0]
+ else:
+ return ret
+
+
+class ShellRenderer(PromptRenderer):
+ '''Powerline shell segment renderer.'''
+ escape_hl_start = ''
+ escape_hl_end = ''
+ term_truecolor = False
+ term_escape_style = 'auto'
+ tmux_escape = False
+ screen_escape = False
+
+ character_translations = Renderer.character_translations.copy()
+
+ def render(self, segment_info, **kwargs):
+ local_theme = segment_info.get('local_theme')
+ return super(ShellRenderer, self).render(
+ matcher_info=local_theme,
+ segment_info=segment_info,
+ **kwargs
+ )
+
+ def do_render(self, segment_info, **kwargs):
+ if self.term_escape_style == 'auto':
+ if segment_info['environ'].get('TERM') == 'fbterm':
+ self.used_term_escape_style = 'fbterm'
+ else:
+ self.used_term_escape_style = 'xterm'
+ else:
+ self.used_term_escape_style = self.term_escape_style
+ return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs)
+
+ def hlstyle(self, fg=None, bg=None, attrs=None, escape=True, **kwargs):
+ '''Highlight a segment.
+
+ If an argument is None, the argument is ignored. If an argument is
+ False, the argument is reset to the terminal defaults. If an argument
+ is a valid color or attribute, it’s added to the ANSI escape code.
+ '''
+ ansi = [0]
+ is_fbterm = self.used_term_escape_style == 'fbterm'
+ term_truecolor = not is_fbterm and self.term_truecolor
+ if fg is not None:
+ if fg is False or fg[0] is False:
+ ansi += [39]
+ else:
+ if term_truecolor:
+ ansi += [38, 2] + list(int_to_rgb(fg[1]))
+ else:
+ ansi += [38, 5, fg[0]]
+ if bg is not None:
+ if bg is False or bg[0] is False:
+ ansi += [49]
+ else:
+ if term_truecolor:
+ ansi += [48, 2] + list(int_to_rgb(bg[1]))
+ else:
+ ansi += [48, 5, bg[0]]
+ if attrs is not None:
+ if attrs is False:
+ ansi += [22]
+ else:
+ if attrs & ATTR_BOLD:
+ ansi += [1]
+ elif attrs & ATTR_ITALIC:
+ # Note: is likely not to work or even be inverse in place of
+ # italic. Omit using this in colorschemes.
+ ansi += [3]
+ elif attrs & ATTR_UNDERLINE:
+ ansi += [4]
+ if is_fbterm:
+ r = []
+ while ansi:
+ cur_ansi = ansi.pop(0)
+ if cur_ansi == 38:
+ ansi.pop(0)
+ r.append('\033[1;{0}}}'.format(ansi.pop(0)))
+ elif cur_ansi == 48:
+ ansi.pop(0)
+ r.append('\033[2;{0}}}'.format(ansi.pop(0)))
+ else:
+ r.append('\033[{0}m'.format(cur_ansi))
+ r = ''.join(r)
+ else:
+ r = '\033[{0}m'.format(';'.join(str(attr) for attr in ansi))
+ if self.tmux_escape:
+ r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\'
+ elif self.screen_escape:
+ r = '\033P' + r.replace('\033', '\033\033') + '\033\\'
+ return self.escape_hl_start + r + self.escape_hl_end if escape else r
+
+ def get_theme(self, matcher_info):
+ if not matcher_info:
+ return self.theme
+ match = self.local_themes[matcher_info]
+ try:
+ return match['theme']
+ except KeyError:
+ match['theme'] = Theme(
+ theme_config=match['config'],
+ main_theme_config=self.theme_config,
+ **self.theme_kwargs
+ )
+ return match['theme']
+
+
+renderer = ShellRenderer
diff --git a/powerline/renderers/shell/bash.py b/powerline/renderers/shell/bash.py
new file mode 100644
index 0000000..5ccf206
--- /dev/null
+++ b/powerline/renderers/shell/bash.py
@@ -0,0 +1,96 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderers.shell import ShellRenderer
+
+
+class BashPromptRenderer(ShellRenderer):
+ '''Powerline bash prompt segment renderer.'''
+ escape_hl_start = '\\['
+ escape_hl_end = '\\]'
+
+ character_translations = ShellRenderer.character_translations.copy()
+ character_translations[ord('$')] = '\\$'
+ character_translations[ord('`')] = '\\`'
+ character_translations[ord('\\')] = '\\\\'
+
+ def do_render(self, side, line, width, output_width, output_raw, hl_args, **kwargs):
+
+ # we are rendering the normal left prompt
+ if side == 'left' and line == 0 and width is not None:
+
+ # we need left prompt's width to render the raw spacer
+ output_width = output_width or output_raw
+
+ left = super(BashPromptRenderer, self).do_render(
+ side=side,
+ line=line,
+ output_width=output_width,
+ width=width,
+ output_raw=output_raw,
+ hl_args=hl_args,
+ **kwargs
+ )
+ left_rendered = left[0] if output_width else left
+
+ # we don't escape color sequences in the right prompt so we can do escaping as a whole
+ if hl_args:
+ hl_args = hl_args.copy()
+ hl_args.update({'escape': False})
+ else:
+ hl_args = {'escape': False}
+
+ right = super(BashPromptRenderer, self).do_render(
+ side='right',
+ line=line,
+ output_width=True,
+ width=width,
+ output_raw=output_raw,
+ hl_args=hl_args,
+ **kwargs
+ )
+
+ ret = []
+ if right[-1] > 0:
+ # if the right prompt is not empty we embed it in the left prompt
+ # it must be escaped as a whole so readline doesn't see it
+ ret.append(''.join((
+ left_rendered,
+ self.escape_hl_start,
+ '\033[s', # save the cursor position
+ '\033[{0}C'.format(width), # move to the right edge of the terminal
+ '\033[{0}D'.format(right[-1] - 1), # move back to the right prompt position
+ right[0],
+ '\033[u', # restore the cursor position
+ self.escape_hl_end
+ )))
+ if output_raw:
+ ret.append(''.join((
+ left[1],
+ ' ' * (width - left[-1] - right[-1]),
+ right[1]
+ )))
+ else:
+ ret.append(left_rendered)
+ if output_raw:
+ ret.append(left[1])
+ if output_width:
+ ret.append(left[-1])
+ if len(ret) == 1:
+ return ret[0]
+ else:
+ return ret
+
+ else:
+ return super(BashPromptRenderer, self).do_render(
+ side=side,
+ line=line,
+ width=width,
+ output_width=output_width,
+ output_raw=output_raw,
+ hl_args=hl_args,
+ **kwargs
+ )
+
+
+renderer = BashPromptRenderer
diff --git a/powerline/renderers/shell/ksh.py b/powerline/renderers/shell/ksh.py
new file mode 100644
index 0000000..0828e57
--- /dev/null
+++ b/powerline/renderers/shell/ksh.py
@@ -0,0 +1,19 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderers.shell import ShellRenderer
+
+
+ESCAPE_CHAR = '\001'
+
+
+class KshPromptRenderer(ShellRenderer):
+ '''Powerline bash prompt segment renderer.'''
+ escape_hl_start = '\001'
+ escape_hl_end = '\001'
+
+ def render(self, *args, **kwargs):
+ return '\001\r' + super(KshPromptRenderer, self).render(*args, **kwargs)
+
+
+renderer = KshPromptRenderer
diff --git a/powerline/renderers/shell/rcsh.py b/powerline/renderers/shell/rcsh.py
new file mode 100644
index 0000000..75ccb22
--- /dev/null
+++ b/powerline/renderers/shell/rcsh.py
@@ -0,0 +1,7 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderers.shell.readline import ReadlineRenderer
+
+
+renderer = ReadlineRenderer
diff --git a/powerline/renderers/shell/readline.py b/powerline/renderers/shell/readline.py
new file mode 100644
index 0000000..a72dff0
--- /dev/null
+++ b/powerline/renderers/shell/readline.py
@@ -0,0 +1,14 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderers.shell import ShellRenderer
+
+
+class ReadlineRenderer(ShellRenderer):
+ '''Renderer useful for some applications that use readline
+ '''
+ escape_hl_start = '\x01'
+ escape_hl_end = '\x02'
+
+
+renderer = ReadlineRenderer
diff --git a/powerline/renderers/shell/tcsh.py b/powerline/renderers/shell/tcsh.py
new file mode 100644
index 0000000..bf0697d
--- /dev/null
+++ b/powerline/renderers/shell/tcsh.py
@@ -0,0 +1,31 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderers.shell.zsh import ZshPromptRenderer
+
+
+class TcshPromptRenderer(ZshPromptRenderer):
+ '''Powerline tcsh prompt segment renderer.'''
+ character_translations = ZshPromptRenderer.character_translations.copy()
+ character_translations[ord('%')] = '%%'
+ character_translations[ord('\\')] = '\\\\'
+ character_translations[ord('^')] = '\\^'
+ character_translations[ord('!')] = '\\!'
+
+ def do_render(self, **kwargs):
+ ret = super(TcshPromptRenderer, self).do_render(**kwargs)
+ nbsp = self.character_translations.get(ord(' '), ' ')
+ end = self.hlstyle()
+ assert not ret or ret.endswith(end)
+ if ret.endswith(nbsp + end):
+ # Exchange nbsp and highlight end because tcsh removes trailing
+ # %{%} part of the prompt for whatever reason
+ ret = ret[:-(len(nbsp) + len(end))] + end + nbsp
+ else:
+ # We *must* end prompt with non-%{%} sequence for the reasons
+ # explained above. So add nbsp if it is not already there.
+ ret += nbsp
+ return ret
+
+
+renderer = TcshPromptRenderer
diff --git a/powerline/renderers/shell/zsh.py b/powerline/renderers/shell/zsh.py
new file mode 100644
index 0000000..a231512
--- /dev/null
+++ b/powerline/renderers/shell/zsh.py
@@ -0,0 +1,16 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderers.shell import ShellRenderer
+
+
+class ZshPromptRenderer(ShellRenderer):
+ '''Powerline zsh prompt segment renderer.'''
+ escape_hl_start = '%{'
+ escape_hl_end = '%}'
+
+ character_translations = ShellRenderer.character_translations.copy()
+ character_translations[ord('%')] = '%%'
+
+
+renderer = ZshPromptRenderer
diff --git a/powerline/renderers/tmux.py b/powerline/renderers/tmux.py
new file mode 100644
index 0000000..fc3282a
--- /dev/null
+++ b/powerline/renderers/tmux.py
@@ -0,0 +1,81 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.renderer import Renderer
+from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
+
+
+def attrs_to_tmux_attrs(attrs):
+ if attrs is False:
+ return ['nobold', 'noitalics', 'nounderscore']
+ else:
+ ret = []
+ if attrs & ATTR_BOLD:
+ ret += ['bold']
+ else:
+ ret += ['nobold']
+ if attrs & ATTR_ITALIC:
+ ret += ['italics']
+ else:
+ ret += ['noitalics']
+ if attrs & ATTR_UNDERLINE:
+ ret += ['underscore']
+ else:
+ ret += ['nounderscore']
+ return ret
+
+
+class TmuxRenderer(Renderer):
+ '''Powerline tmux segment renderer.'''
+
+ character_translations = Renderer.character_translations.copy()
+ character_translations[ord('#')] = '##[]'
+
+ def render(self, width=None, segment_info={}, **kwargs):
+ if width and segment_info:
+ width -= segment_info.get('width_adjust', 0)
+ if width < 10:
+ width = 10
+ return super(TmuxRenderer, self).render(width=width, segment_info=segment_info, **kwargs)
+
+ def hlstyle(self, fg=None, bg=None, attrs=None, **kwargs):
+ '''Highlight a segment.'''
+ # We don’t need to explicitly reset attributes, so skip those calls
+ if not attrs and not bg and not fg:
+ return ''
+ tmux_attrs = []
+ if fg is not None:
+ if fg is False or fg[0] is False:
+ tmux_attrs += ['fg=default']
+ else:
+ if self.term_truecolor and fg[1]:
+ tmux_attrs += ['fg=#{0:06x}'.format(int(fg[1]))]
+ else:
+ tmux_attrs += ['fg=colour' + str(fg[0])]
+ if bg is not None:
+ if bg is False or bg[0] is False:
+ tmux_attrs += ['bg=default']
+ else:
+ if self.term_truecolor and bg[1]:
+ tmux_attrs += ['bg=#{0:06x}'.format(int(bg[1]))]
+ else:
+ tmux_attrs += ['bg=colour' + str(bg[0])]
+ if attrs is not None:
+ tmux_attrs += attrs_to_tmux_attrs(attrs)
+ return '#[' + ','.join(tmux_attrs) + ']'
+
+ def get_segment_info(self, segment_info, mode):
+ r = self.segment_info.copy()
+ if segment_info:
+ r.update(segment_info)
+ if 'pane_current_path' in r:
+ r['getcwd'] = lambda: r['pane_current_path']
+ elif 'pane_id' in r:
+ varname = 'TMUX_PWD_' + str(r['pane_id'])
+ if varname in r['environ']:
+ r['getcwd'] = lambda: r['environ'][varname]
+ r['mode'] = mode
+ return r
+
+
+renderer = TmuxRenderer
diff --git a/powerline/renderers/vim.py b/powerline/renderers/vim.py
new file mode 100644
index 0000000..a92d51c
--- /dev/null
+++ b/powerline/renderers/vim.py
@@ -0,0 +1,188 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+
+import vim
+
+from powerline.bindings.vim import vim_get_func, vim_getoption, environ, current_tabpage, get_vim_encoding
+from powerline.renderer import Renderer
+from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
+from powerline.theme import Theme
+from powerline.lib.unicode import unichr, register_strwidth_error
+
+
+vim_mode = vim_get_func('mode', rettype='unicode')
+if int(vim.eval('v:version')) >= 702:
+ _vim_mode = vim_mode
+ vim_mode = lambda: _vim_mode(1)
+
+mode_translations = {
+ unichr(ord('V') - 0x40): '^V',
+ unichr(ord('S') - 0x40): '^S',
+}
+
+
+class VimRenderer(Renderer):
+ '''Powerline vim segment renderer.'''
+
+ character_translations = Renderer.character_translations.copy()
+ character_translations[ord('%')] = '%%'
+
+ segment_info = Renderer.segment_info.copy()
+ segment_info.update(environ=environ)
+
+ def __init__(self, *args, **kwargs):
+ if not hasattr(vim, 'strwidth'):
+ # Hope nobody want to change this at runtime
+ if vim.eval('&ambiwidth') == 'double':
+ kwargs = dict(**kwargs)
+ kwargs['ambigious'] = 2
+ super(VimRenderer, self).__init__(*args, **kwargs)
+ self.hl_groups = {}
+ self.prev_highlight = None
+ self.strwidth_error_name = register_strwidth_error(self.strwidth)
+ self.encoding = get_vim_encoding()
+
+ def shutdown(self):
+ self.theme.shutdown()
+ for match in self.local_themes.values():
+ if 'theme' in match:
+ match['theme'].shutdown()
+
+ def add_local_theme(self, matcher, theme):
+ if matcher in self.local_themes:
+ raise KeyError('There is already a local theme with given matcher')
+ self.local_themes[matcher] = theme
+
+ def get_matched_theme(self, match):
+ try:
+ return match['theme']
+ except KeyError:
+ match['theme'] = Theme(theme_config=match['config'], main_theme_config=self.theme_config, **self.theme_kwargs)
+ return match['theme']
+
+ def get_theme(self, matcher_info):
+ if matcher_info is None:
+ return self.get_matched_theme(self.local_themes[None])
+ for matcher in self.local_themes.keys():
+ if matcher and matcher(matcher_info):
+ return self.get_matched_theme(self.local_themes[matcher])
+ else:
+ return self.theme
+
+ if hasattr(vim, 'strwidth'):
+ if sys.version_info < (3,):
+ def strwidth(self, string):
+ # Does not work with tabs, but neither is strwidth from default
+ # renderer
+ return vim.strwidth(string.encode(self.encoding, 'replace'))
+ else:
+ @staticmethod
+ def strwidth(string):
+ return vim.strwidth(string)
+
+ def get_segment_info(self, segment_info, mode):
+ return segment_info or self.segment_info
+
+ def render(self, window=None, window_id=None, winnr=None, is_tabline=False):
+ '''Render all segments.'''
+ segment_info = self.segment_info.copy()
+
+ if window is vim.current.window:
+ mode = vim_mode()
+ mode = mode_translations.get(mode, mode)
+ else:
+ mode = 'nc'
+
+ segment_info.update(
+ window=window,
+ mode=mode,
+ window_id=window_id,
+ winnr=winnr,
+ buffer=window.buffer,
+ tabpage=current_tabpage(),
+ encoding=self.encoding,
+ )
+ segment_info['tabnr'] = segment_info['tabpage'].number
+ segment_info['bufnr'] = segment_info['buffer'].number
+ if is_tabline:
+ winwidth = int(vim_getoption('columns'))
+ else:
+ winwidth = segment_info['window'].width
+
+ statusline = super(VimRenderer, self).render(
+ mode=mode,
+ width=winwidth,
+ segment_info=segment_info,
+ matcher_info=(None if is_tabline else segment_info),
+ )
+ statusline = statusline.encode(self.encoding, self.strwidth_error_name)
+ return statusline
+
+ def reset_highlight(self):
+ self.hl_groups.clear()
+
+ def hlstyle(self, fg=None, bg=None, attrs=None, **kwargs):
+ '''Highlight a segment.
+
+ If an argument is None, the argument is ignored. If an argument is
+ False, the argument is reset to the terminal defaults. If an argument
+ is a valid color or attribute, it’s added to the vim highlight group.
+ '''
+ # In order not to hit E541 two consequent identical highlighting
+ # specifiers may be squashed into one.
+ attrs = attrs or 0 # Normalize `attrs`
+ if (fg, bg, attrs) == self.prev_highlight:
+ return ''
+ self.prev_highlight = (fg, bg, attrs)
+
+ # We don’t need to explicitly reset attributes in vim, so skip those
+ # calls
+ if not attrs and not bg and not fg:
+ return ''
+
+ if not (fg, bg, attrs) in self.hl_groups:
+ hl_group = {
+ 'ctermfg': 'NONE',
+ 'guifg': None,
+ 'ctermbg': 'NONE',
+ 'guibg': None,
+ 'attrs': ['NONE'],
+ 'name': '',
+ }
+ if fg is not None and fg is not False:
+ hl_group['ctermfg'] = fg[0]
+ hl_group['guifg'] = fg[1]
+ if bg is not None and bg is not False:
+ hl_group['ctermbg'] = bg[0]
+ hl_group['guibg'] = bg[1]
+ if attrs:
+ hl_group['attrs'] = []
+ if attrs & ATTR_BOLD:
+ hl_group['attrs'].append('bold')
+ if attrs & ATTR_ITALIC:
+ hl_group['attrs'].append('italic')
+ if attrs & ATTR_UNDERLINE:
+ hl_group['attrs'].append('underline')
+ hl_group['name'] = (
+ 'Pl_'
+ + str(hl_group['ctermfg']) + '_'
+ + str(hl_group['guifg']) + '_'
+ + str(hl_group['ctermbg']) + '_'
+ + str(hl_group['guibg']) + '_'
+ + ''.join(hl_group['attrs'])
+ )
+ self.hl_groups[(fg, bg, attrs)] = hl_group
+ vim.command('hi {group} ctermfg={ctermfg} guifg={guifg} guibg={guibg} ctermbg={ctermbg} cterm={attrs} gui={attrs}'.format(
+ group=hl_group['name'],
+ ctermfg=hl_group['ctermfg'],
+ guifg='#{0:06x}'.format(hl_group['guifg']) if hl_group['guifg'] is not None else 'NONE',
+ ctermbg=hl_group['ctermbg'],
+ guibg='#{0:06x}'.format(hl_group['guibg']) if hl_group['guibg'] is not None else 'NONE',
+ attrs=','.join(hl_group['attrs']),
+ ))
+ return '%#' + self.hl_groups[(fg, bg, attrs)]['name'] + '#'
+
+
+renderer = VimRenderer
diff --git a/powerline/segment.py b/powerline/segment.py
new file mode 100644
index 0000000..c83bf6f
--- /dev/null
+++ b/powerline/segment.py
@@ -0,0 +1,450 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lib.watcher import create_file_watcher
+
+
+def list_segment_key_values(segment, theme_configs, segment_data, key, function_name=None, name=None, module=None, default=None):
+ try:
+ yield segment[key]
+ except KeyError:
+ pass
+ found_module_key = False
+ for theme_config in theme_configs:
+ try:
+ segment_data = theme_config['segment_data']
+ except KeyError:
+ pass
+ else:
+ if function_name and not name:
+ if module:
+ try:
+ yield segment_data[module + '.' + function_name][key]
+ found_module_key = True
+ except KeyError:
+ pass
+ if not found_module_key:
+ try:
+ yield segment_data[function_name][key]
+ except KeyError:
+ pass
+ if name:
+ try:
+ yield segment_data[name][key]
+ except KeyError:
+ pass
+ if segment_data is not None:
+ try:
+ yield segment_data[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):
+ function_name = segment['function']
+ if '.' in function_name:
+ module, function_name = function_name.rpartition('.')[::2]
+ else:
+ module = data['default_module']
+ function = data['get_module_attr'](module, function_name, prefix='segment_generator')
+ if not function:
+ raise ImportError('Failed to obtain segment function')
+ return None, function, module, function_name, segment.get('name')
+
+
+def get_string(data, segment):
+ name = segment.get('name')
+ return data['get_key'](False, segment, None, None, name, 'contents'), None, None, None, name
+
+
+segment_getters = {
+ 'function': get_function,
+ 'string': get_string,
+ 'segment_list': get_function,
+}
+
+
+def get_attr_func(contents_func, key, args, is_space_func=False):
+ try:
+ func = getattr(contents_func, key)
+ except AttributeError:
+ return None
+ else:
+ if is_space_func:
+ def expand_func(pl, amount, segment):
+ try:
+ return func(pl=pl, amount=amount, segment=segment, **args)
+ except Exception as e:
+ pl.exception('Exception while computing {0} function: {1}', key, str(e))
+ return segment['contents'] + (' ' * amount)
+ return expand_func
+ else:
+ return lambda pl, shutdown_event: func(pl=pl, shutdown_event=shutdown_event, **args)
+
+
+def process_segment_lister(pl, segment_info, parsed_segments, side, mode, colorscheme,
+ lister, subsegments, patcher_args):
+ subsegments = [
+ subsegment
+ for subsegment in subsegments
+ if subsegment['display_condition'](pl, segment_info, mode)
+ ]
+ for subsegment_info, subsegment_update in lister(pl=pl, segment_info=segment_info, **patcher_args):
+ draw_inner_divider = subsegment_update.pop('draw_inner_divider', False)
+ old_pslen = len(parsed_segments)
+ for subsegment in subsegments:
+ if subsegment_update:
+ subsegment = subsegment.copy()
+ subsegment.update(subsegment_update)
+ if 'priority_multiplier' in subsegment_update and subsegment['priority']:
+ subsegment['priority'] *= subsegment_update['priority_multiplier']
+
+ process_segment(
+ pl,
+ side,
+ subsegment_info,
+ parsed_segments,
+ subsegment,
+ mode,
+ colorscheme,
+ )
+ new_pslen = len(parsed_segments)
+ while parsed_segments[new_pslen - 1]['literal_contents'][1]:
+ new_pslen -= 1
+ if new_pslen > old_pslen + 1 and draw_inner_divider is not None:
+ for i in range(old_pslen, new_pslen - 1) if side == 'left' else range(old_pslen + 1, new_pslen):
+ parsed_segments[i]['draw_soft_divider'] = draw_inner_divider
+ return None
+
+
+def set_segment_highlighting(pl, colorscheme, segment, mode):
+ if segment['literal_contents'][1]:
+ return True
+ try:
+ highlight_group_prefix = segment['highlight_group_prefix']
+ except KeyError:
+ hl_groups = lambda hlgs: hlgs
+ else:
+ hl_groups = lambda hlgs: [highlight_group_prefix + ':' + hlg for hlg in hlgs] + hlgs
+ try:
+ segment['highlight'] = colorscheme.get_highlighting(
+ hl_groups(segment['highlight_groups']),
+ mode,
+ segment.get('gradient_level')
+ )
+ if segment['divider_highlight_group']:
+ segment['divider_highlight'] = colorscheme.get_highlighting(
+ hl_groups([segment['divider_highlight_group']]),
+ mode
+ )
+ else:
+ segment['divider_highlight'] = None
+ except Exception as e:
+ pl.exception('Failed to set highlight group: {0}', str(e))
+ return False
+ else:
+ return True
+
+
+def process_segment(pl, side, segment_info, parsed_segments, segment, mode, colorscheme):
+ segment = segment.copy()
+ pl.prefix = segment['name']
+ if segment['type'] in ('function', 'segment_list'):
+ try:
+ if segment['type'] == 'function':
+ contents = segment['contents_func'](pl, segment_info)
+ else:
+ contents = segment['contents_func'](pl, segment_info, parsed_segments, side, mode, colorscheme)
+ except Exception as e:
+ pl.exception('Exception while computing segment: {0}', str(e))
+ return
+
+ if contents is None:
+ return
+
+ if isinstance(contents, list):
+ # Needs copying here, but it was performed at the very start of the
+ # function
+ segment_base = segment
+ if contents:
+ draw_divider_position = -1 if side == 'left' else 0
+ for key, i, newval in (
+ ('before', 0, ''),
+ ('after', -1, ''),
+ ('draw_soft_divider', draw_divider_position, True),
+ ('draw_hard_divider', draw_divider_position, True),
+ ):
+ try:
+ contents[i][key] = segment_base.pop(key)
+ segment_base[key] = newval
+ except KeyError:
+ pass
+
+ draw_inner_divider = None
+ if side == 'right':
+ append = parsed_segments.append
+ else:
+ pslen = len(parsed_segments)
+ append = lambda item: parsed_segments.insert(pslen, item)
+
+ for subsegment in (contents if side == 'right' else reversed(contents)):
+ segment_copy = segment_base.copy()
+ segment_copy.update(subsegment)
+ if draw_inner_divider is not None:
+ segment_copy['draw_soft_divider'] = draw_inner_divider
+ draw_inner_divider = segment_copy.pop('draw_inner_divider', None)
+ if set_segment_highlighting(pl, colorscheme, segment_copy, mode):
+ append(segment_copy)
+ else:
+ segment['contents'] = contents
+ if set_segment_highlighting(pl, colorscheme, segment, mode):
+ parsed_segments.append(segment)
+ elif segment['width'] == 'auto' or (segment['type'] == 'string' and segment['contents'] is not None):
+ if set_segment_highlighting(pl, colorscheme, segment, mode):
+ parsed_segments.append(segment)
+
+
+always_true = lambda pl, segment_info, mode: True
+
+get_fallback_segment = {
+ 'name': 'fallback',
+ 'type': 'string',
+ 'highlight_groups': ['background'],
+ 'divider_highlight_group': None,
+ 'before': None,
+ 'after': None,
+ 'contents': '',
+ 'literal_contents': (0, ''),
+ 'priority': None,
+ 'draw_soft_divider': True,
+ 'draw_hard_divider': True,
+ 'draw_inner_divider': True,
+ 'display_condition': always_true,
+ 'width': None,
+ 'align': None,
+ 'expand': None,
+ 'truncate': None,
+ 'startup': None,
+ 'shutdown': None,
+ '_rendered_raw': '',
+ '_rendered_hl': '',
+ '_len': None,
+ '_contents_len': None,
+}.copy
+
+
+def gen_segment_getter(pl, ext, common_config, theme_configs, default_module, get_module_attr, top_theme):
+ data = {
+ 'default_module': default_module or 'powerline.segments.' + ext,
+ 'get_module_attr': get_module_attr,
+ 'segment_data': None,
+ }
+
+ def get_key(merge, segment, module, function_name, name, key, default=None):
+ return get_segment_key(merge, segment, theme_configs, data['segment_data'], key, function_name, name, module, default)
+ data['get_key'] = get_key
+
+ def get_selector(function_name):
+ if '.' in function_name:
+ module, function_name = function_name.rpartition('.')[::2]
+ else:
+ module = 'powerline.selectors.' + ext
+ function = get_module_attr(module, function_name, prefix='segment_generator/selector_function')
+ if not function:
+ pl.error('Failed to get segment selector, ignoring it')
+ return function
+
+ def get_segment_selector(segment, selector_type):
+ try:
+ function_name = segment[selector_type + '_function']
+ except KeyError:
+ function = None
+ else:
+ function = get_selector(function_name)
+ try:
+ modes = segment[selector_type + '_modes']
+ except KeyError:
+ modes = None
+
+ if modes:
+ if function:
+ return lambda pl, segment_info, mode: (
+ mode in modes
+ or function(pl=pl, segment_info=segment_info, mode=mode)
+ )
+ else:
+ return lambda pl, segment_info, mode: mode in modes
+ else:
+ if function:
+ return lambda pl, segment_info, mode: (
+ function(pl=pl, segment_info=segment_info, mode=mode)
+ )
+ else:
+ return None
+
+ def gen_display_condition(segment):
+ include_function = get_segment_selector(segment, 'include')
+ exclude_function = get_segment_selector(segment, 'exclude')
+ if include_function:
+ if exclude_function:
+ return lambda *args: (
+ include_function(*args)
+ and not exclude_function(*args))
+ else:
+ return include_function
+ else:
+ if exclude_function:
+ return lambda *args: not exclude_function(*args)
+ else:
+ return always_true
+
+ def get(segment, side):
+ segment_type = segment.get('type', 'function')
+ try:
+ get_segment_info = segment_getters[segment_type]
+ except KeyError:
+ pl.error('Unknown segment type: {0}', segment_type)
+ return None
+
+ try:
+ contents, _contents_func, module, function_name, name = get_segment_info(data, segment)
+ except Exception as e:
+ pl.exception('Failed to generate segment from {0!r}: {1}', segment, str(e), prefix='segment_generator')
+ return None
+
+ if not get_key(False, segment, module, function_name, name, 'display', True):
+ return None
+
+ segment_datas = getattr(_contents_func, 'powerline_segment_datas', None)
+ if segment_datas:
+ try:
+ data['segment_data'] = segment_datas[top_theme]
+ except KeyError:
+ pass
+
+ if segment_type == 'function':
+ highlight_groups = [function_name]
+ else:
+ highlight_groups = segment.get('highlight_groups') or [name]
+
+ if segment_type in ('function', 'segment_list'):
+ args = dict((
+ (str(k), v)
+ for k, v in
+ get_key(True, segment, module, function_name, name, 'args', {}).items()
+ ))
+
+ display_condition = gen_display_condition(segment)
+
+ if segment_type == 'segment_list':
+ # Handle startup and shutdown of _contents_func?
+ subsegments = [
+ subsegment
+ for subsegment in (
+ get(subsegment, side)
+ for subsegment in segment['segments']
+ ) if subsegment
+ ]
+ return {
+ 'name': name or function_name,
+ 'type': segment_type,
+ 'highlight_groups': None,
+ 'divider_highlight_group': None,
+ 'before': None,
+ 'after': None,
+ 'contents_func': lambda pl, segment_info, parsed_segments, side, mode, colorscheme: (
+ process_segment_lister(
+ pl, segment_info, parsed_segments, side, mode, colorscheme,
+ patcher_args=args,
+ subsegments=subsegments,
+ lister=_contents_func,
+ )
+ ),
+ 'contents': None,
+ 'literal_contents': None,
+ 'priority': None,
+ 'draw_soft_divider': None,
+ 'draw_hard_divider': None,
+ 'draw_inner_divider': None,
+ 'side': side,
+ 'display_condition': display_condition,
+ 'width': None,
+ 'align': None,
+ 'expand': None,
+ 'truncate': None,
+ 'startup': None,
+ 'shutdown': None,
+ '_rendered_raw': '',
+ '_rendered_hl': '',
+ '_len': None,
+ '_contents_len': None,
+ }
+
+ if segment_type == 'function':
+ startup_func = get_attr_func(_contents_func, 'startup', args)
+ shutdown_func = getattr(_contents_func, 'shutdown', None)
+ expand_func = get_attr_func(_contents_func, 'expand', args, True)
+ truncate_func = get_attr_func(_contents_func, 'truncate', args, True)
+
+ if hasattr(_contents_func, 'powerline_requires_filesystem_watcher'):
+ create_watcher = lambda: create_file_watcher(pl, common_config['watcher'])
+ args[str('create_watcher')] = create_watcher
+
+ if hasattr(_contents_func, 'powerline_requires_segment_info'):
+ contents_func = lambda pl, segment_info: _contents_func(pl=pl, segment_info=segment_info, **args)
+ else:
+ contents_func = lambda pl, segment_info: _contents_func(pl=pl, **args)
+ else:
+ startup_func = None
+ shutdown_func = None
+ contents_func = None
+ expand_func = None
+ truncate_func = None
+
+ return {
+ 'name': name or function_name,
+ 'type': segment_type,
+ 'highlight_groups': highlight_groups,
+ 'divider_highlight_group': None,
+ 'before': get_key(False, segment, module, function_name, name, 'before', ''),
+ 'after': get_key(False, segment, module, function_name, name, 'after', ''),
+ 'contents_func': contents_func,
+ 'contents': contents,
+ 'literal_contents': (0, ''),
+ 'priority': segment.get('priority', None),
+ 'draw_hard_divider': segment.get('draw_hard_divider', True),
+ 'draw_soft_divider': segment.get('draw_soft_divider', True),
+ 'draw_inner_divider': segment.get('draw_inner_divider', False),
+ 'side': side,
+ 'display_condition': display_condition,
+ 'width': segment.get('width'),
+ 'align': segment.get('align', 'l'),
+ 'expand': expand_func,
+ 'truncate': truncate_func,
+ 'startup': startup_func,
+ 'shutdown': shutdown_func,
+ '_rendered_raw': '',
+ '_rendered_hl': '',
+ '_len': None,
+ '_contents_len': None,
+ }
+
+ return get
diff --git a/powerline/segments/__init__.py b/powerline/segments/__init__.py
new file mode 100644
index 0000000..fa09e58
--- /dev/null
+++ b/powerline/segments/__init__.py
@@ -0,0 +1,63 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+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: it defines methods for
+ omitting existing or adding new arguments.
+
+ .. note::
+ Until python-3.4 ``inspect.getargspec`` does not support querying
+ callable classes for arguments of their ``__call__`` method, requiring
+ to use this method directly (i.e. before 3.4 you should write
+ ``getargspec(obj.__call__)`` in place of ``getargspec(obj)``).
+ '''
+ if sys.version_info < (3, 4):
+ def argspecobjs(self):
+ yield '__call__', self.__call__
+ else:
+ def argspecobjs(self):
+ 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 ()
+
+
+def with_docstring(instance, doc):
+ instance.__doc__ = doc
+ return instance
diff --git a/powerline/segments/common/__init__.py b/powerline/segments/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/segments/common/__init__.py
diff --git a/powerline/segments/common/bat.py b/powerline/segments/common/bat.py
new file mode 100644
index 0000000..c892f62
--- /dev/null
+++ b/powerline/segments/common/bat.py
@@ -0,0 +1,302 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import sys
+import re
+
+from powerline.lib.shell import run_cmd
+
+
+def _fetch_battery_info(pl):
+ try:
+ import dbus
+ except ImportError:
+ pl.debug('Not using DBUS+UPower as dbus is not available')
+ else:
+ try:
+ bus = dbus.SystemBus()
+ except Exception as e:
+ pl.exception('Failed to connect to system bus: {0}', str(e))
+ else:
+ interface = 'org.freedesktop.UPower'
+ try:
+ up = bus.get_object(interface, '/org/freedesktop/UPower')
+ except dbus.exceptions.DBusException as e:
+ if getattr(e, '_dbus_error_name', '').endswith('ServiceUnknown'):
+ pl.debug('Not using DBUS+UPower as UPower is not available via dbus')
+ else:
+ pl.exception('Failed to get UPower service with dbus: {0}', str(e))
+ else:
+ devinterface = 'org.freedesktop.DBus.Properties'
+ devtype_name = interface + '.Device'
+ devices = []
+ for devpath in up.EnumerateDevices(dbus_interface=interface):
+ dev = bus.get_object(interface, devpath)
+ devget = lambda what: dev.Get(
+ devtype_name,
+ what,
+ dbus_interface=devinterface
+ )
+ if int(devget('Type')) != 2:
+ pl.debug('Not using DBUS+UPower with {0}: invalid type', devpath)
+ continue
+ if not bool(devget('IsPresent')):
+ pl.debug('Not using DBUS+UPower with {0}: not present', devpath)
+ continue
+ if not bool(devget('PowerSupply')):
+ pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath)
+ continue
+ devices.append(devpath)
+ pl.debug('Using DBUS+UPower with {0}', devpath)
+ if devices:
+ def _flatten_battery(pl):
+ energy = 0.0
+ energy_full = 0.0
+ state = True
+ for devpath in devices:
+ dev = bus.get_object(interface, devpath)
+ energy_full += float(
+ dbus.Interface(dev, dbus_interface=devinterface).Get(
+ devtype_name,
+ 'EnergyFull'
+ ),
+ )
+ energy += float(
+ dbus.Interface(dev, dbus_interface=devinterface).Get(
+ devtype_name,
+ 'Energy'
+ ),
+ )
+ state &= dbus.Interface(dev, dbus_interface=devinterface).Get(
+ devtype_name,
+ 'State'
+ ) != 2
+ if energy_full > 0:
+ return (energy * 100.0 / energy_full), state
+ else:
+ return 0.0, state
+ return _flatten_battery
+ pl.debug('Not using DBUS+UPower as no batteries were found')
+
+ if os.path.isdir('/sys/class/power_supply'):
+ # ENERGY_* attributes represents capacity in µWh only.
+ # CHARGE_* attributes represents capacity in µAh only.
+ linux_capacity_units = ('energy', 'charge')
+ linux_energy_full_fmt = '/sys/class/power_supply/{0}/{1}_full'
+ linux_energy_fmt = '/sys/class/power_supply/{0}/{1}_now'
+ linux_status_fmt = '/sys/class/power_supply/{0}/status'
+ devices = []
+ for linux_supplier in os.listdir('/sys/class/power_supply'):
+ for unit in linux_capacity_units:
+ energy_path = linux_energy_fmt.format(linux_supplier, unit)
+ if not os.path.exists(energy_path):
+ continue
+ pl.debug('Using /sys/class/power_supply with battery {0} and unit {1}',
+ linux_supplier, unit)
+ devices.append((linux_supplier, unit))
+ break # energy or charge, not both
+ if devices:
+ def _get_battery_status(pl):
+ energy = 0.0
+ energy_full = 0.0
+ state = True
+ for device, unit in devices:
+ with open(linux_energy_full_fmt.format(device, unit), 'r') as f:
+ energy_full += int(float(f.readline().split()[0]))
+ with open(linux_energy_fmt.format(device, unit), 'r') as f:
+ energy += int(float(f.readline().split()[0]))
+ try:
+ with open(linux_status_fmt.format(device), 'r') as f:
+ state &= (f.readline().strip() != 'Discharging')
+ except IOError:
+ state = None
+ return (energy * 100.0 / energy_full), state
+ return _get_battery_status
+ pl.debug('Not using /sys/class/power_supply as no batteries were found')
+ else:
+ pl.debug("Checking for first capacity battery percentage")
+ for batt in os.listdir('/sys/class/power_supply'):
+ if os.path.exists('/sys/class/power_supply/{0}/capacity'.format(batt)):
+ def _get_battery_perc(pl):
+ state = True
+ with open('/sys/class/power_supply/{0}/capacity'.format(batt), 'r') as f:
+ perc = int(f.readline().split()[0])
+ try:
+ with open(linux_status_fmt.format(batt), 'r') as f:
+ state &= (f.readline().strip() != 'Discharging')
+ except IOError:
+ state = None
+ return perc, state
+ return _get_battery_perc
+ else:
+ pl.debug('Not using /sys/class/power_supply: no directory')
+
+ try:
+ from shutil import which # Python-3.3 and later
+ except ImportError:
+ pl.info('Using dumb “which” which only checks for file in /usr/bin')
+ which = lambda f: (lambda fp: os.path.exists(fp) and fp)(os.path.join('/usr/bin', f))
+
+ if which('pmset'):
+ pl.debug('Using pmset')
+
+ BATTERY_PERCENT_RE = re.compile(r'(\d+)%')
+
+ def _get_battery_status(pl):
+ battery_summary = run_cmd(pl, ['pmset', '-g', 'batt'])
+ battery_percent = BATTERY_PERCENT_RE.search(battery_summary).group(1)
+ ac_charging = 'AC' in battery_summary
+ return int(battery_percent), ac_charging
+ return _get_battery_status
+ else:
+ pl.debug('Not using pmset: executable not found')
+
+ if sys.platform.startswith('win') or sys.platform == 'cygwin':
+ # From http://stackoverflow.com/a/21083571/273566, reworked
+ try:
+ from win32com.client import GetObject
+ except ImportError:
+ pl.debug('Not using win32com.client as it is not available')
+ else:
+ try:
+ wmi = GetObject('winmgmts:')
+ except Exception as e:
+ pl.exception('Failed to run GetObject from win32com.client: {0}', str(e))
+ else:
+ for battery in wmi.InstancesOf('Win32_Battery'):
+ pl.debug('Using win32com.client with Win32_Battery')
+
+ def _get_battery_status(pl):
+ # http://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx
+ return battery.EstimatedChargeRemaining, battery.BatteryStatus == 6
+
+ return _get_battery_status
+ pl.debug('Not using win32com.client as no batteries were found')
+ from ctypes import Structure, c_byte, c_ulong, byref
+ if sys.platform == 'cygwin':
+ pl.debug('Using cdll to communicate with kernel32 (Cygwin)')
+ from ctypes import cdll
+ library_loader = cdll
+ else:
+ pl.debug('Using windll to communicate with kernel32 (Windows)')
+ from ctypes import windll
+ library_loader = windll
+
+ class PowerClass(Structure):
+ _fields_ = [
+ ('ACLineStatus', c_byte),
+ ('BatteryFlag', c_byte),
+ ('BatteryLifePercent', c_byte),
+ ('Reserved1', c_byte),
+ ('BatteryLifeTime', c_ulong),
+ ('BatteryFullLifeTime', c_ulong)
+ ]
+
+ def _get_battery_status(pl):
+ powerclass = PowerClass()
+ result = library_loader.kernel32.GetSystemPowerStatus(byref(powerclass))
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa372693(v=vs.85).aspx
+ if result:
+ return None
+ return powerclass.BatteryLifePercent, powerclass.ACLineStatus == 1
+
+ if _get_battery_status() is None:
+ pl.debug('Not using GetSystemPowerStatus because it failed')
+ else:
+ pl.debug('Using GetSystemPowerStatus')
+
+ return _get_battery_status
+
+ raise NotImplementedError
+
+
+def _get_battery_status(pl):
+ global _get_battery_status
+
+ def _failing_get_status(pl):
+ raise NotImplementedError
+
+ try:
+ _get_battery_status = _fetch_battery_info(pl)
+ except NotImplementedError:
+ _get_battery_status = _failing_get_status
+ except Exception as e:
+ pl.exception('Exception while obtaining battery status: {0}', str(e))
+ _get_battery_status = _failing_get_status
+ return _get_battery_status(pl)
+
+
+def battery(pl, format='{ac_state} {capacity:3.0%}', steps=5, gamify=False, full_heart='O', empty_heart='O', online='C', offline=' '):
+ '''Return battery charge status.
+
+ :param str format:
+ Percent format in case gamify is False. Format arguments: ``ac_state``
+ which is equal to either ``online`` or ``offline`` string arguments and
+ ``capacity`` which is equal to current battery capacity in interval [0,
+ 100].
+ :param int steps:
+ Number of discrete steps to show between 0% and 100% capacity if gamify
+ is True.
+ :param bool gamify:
+ Measure in hearts (♥) instead of percentages. For full hearts
+ ``battery_full`` highlighting group is preferred, for empty hearts there
+ is ``battery_empty``. ``battery_online`` or ``battery_offline`` group
+ will be used for leading segment containing ``online`` or ``offline``
+ argument contents.
+ :param str full_heart:
+ Heart displayed for “full” part of battery.
+ :param str empty_heart:
+ Heart displayed for “used” part of battery. It is also displayed using
+ another gradient level and highlighting group, so it is OK for it to be
+ the same as full_heart as long as necessary highlighting groups are
+ defined.
+ :param str online:
+ Symbol used if computer is connected to a power supply.
+ :param str offline:
+ Symbol used if computer is not connected to a power supply.
+
+ ``battery_gradient`` and ``battery`` groups are used in any case, first is
+ preferred.
+
+ Highlight groups used: ``battery_full`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_empty`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_online`` or ``battery_ac_state`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_offline`` or ``battery_ac_state`` or ``battery_gradient`` (gradient) or ``battery``.
+ '''
+ try:
+ capacity, ac_powered = _get_battery_status(pl)
+ except NotImplementedError:
+ pl.info('Unable to get battery status.')
+ return None
+
+ ret = []
+ if gamify:
+ denom = int(steps)
+ numer = int(denom * capacity / 100)
+ ret.append({
+ 'contents': online if ac_powered else offline,
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_online' if ac_powered else 'battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'],
+ 'gradient_level': 0,
+ })
+ ret.append({
+ 'contents': full_heart * numer,
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_full', 'battery_gradient', 'battery'],
+ # Using zero as “nothing to worry about”: it is least alert color.
+ 'gradient_level': 0,
+ })
+ ret.append({
+ 'contents': empty_heart * (denom - numer),
+ 'draw_inner_divider': False,
+ 'highlight_groups': ['battery_empty', 'battery_gradient', 'battery'],
+ # Using a hundred as it is most alert color.
+ 'gradient_level': 100,
+ })
+ else:
+ ret.append({
+ 'contents': format.format(ac_state=(online if ac_powered else offline), capacity=(capacity / 100.0)),
+ 'highlight_groups': ['battery_gradient', 'battery'],
+ # Gradients are “least alert – most alert” by default, capacity has
+ # the opposite semantics.
+ 'gradient_level': 100 - capacity,
+ })
+ return ret
diff --git a/powerline/segments/common/env.py b/powerline/segments/common/env.py
new file mode 100644
index 0000000..bbfe3e2
--- /dev/null
+++ b/powerline/segments/common/env.py
@@ -0,0 +1,201 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from powerline.lib.unicode import out_u
+from powerline.theme import requires_segment_info
+from powerline.segments import Segment, with_docstring
+
+
+@requires_segment_info
+def environment(pl, segment_info, variable=None):
+ '''Return the value of any defined environment variable
+
+ :param string variable:
+ The environment variable to return if found
+ '''
+ return segment_info['environ'].get(variable, None)
+
+
+@requires_segment_info
+def virtualenv(pl, segment_info, ignore_venv=False, ignore_conda=False, ignored_names=("venv", ".venv")):
+ '''Return the name of the current Python or conda virtualenv.
+ :param list ignored_names:
+ Names of venvs to ignore. Will then get the name of the venv by ascending to the parent directory
+ :param bool ignore_venv:
+ Whether to ignore virtual environments. Default is False.
+ :param bool ignore_conda:
+ Whether to ignore conda environments. Default is False.
+ '''
+ if not ignore_venv:
+ for candidate in reversed(segment_info['environ'].get('VIRTUAL_ENV', '').split("/")):
+ if candidate and candidate not in ignored_names:
+ return candidate
+ if not ignore_conda:
+ for candidate in reversed(segment_info['environ'].get('CONDA_DEFAULT_ENV', '').split("/")):
+ if candidate and candidate not in ignored_names:
+ return candidate
+ return None
+
+
+@requires_segment_info
+class CwdSegment(Segment):
+ def argspecobjs(self):
+ for obj in super(CwdSegment, self).argspecobjs():
+ yield obj
+ yield 'get_shortened_path', self.get_shortened_path
+
+ def omitted_args(self, name, method):
+ if method is self.get_shortened_path:
+ return ()
+ else:
+ return super(CwdSegment, self).omitted_args(name, method)
+
+ def get_shortened_path(self, pl, segment_info, shorten_home=True, **kwargs):
+ try:
+ path = out_u(segment_info['getcwd']())
+ except OSError as e:
+ if e.errno == 2:
+ # user most probably deleted the directory
+ # this happens when removing files from Mercurial repos for example
+ pl.warn('Current directory not found')
+ return '[not found]'
+ else:
+ raise
+ if shorten_home:
+ home = segment_info['home']
+ if home:
+ home = out_u(home)
+ if path.startswith(home):
+ path = '~' + path[len(home):]
+ return path
+
+ def __call__(self, pl, segment_info,
+ dir_shorten_len=None,
+ dir_limit_depth=None,
+ use_path_separator=False,
+ ellipsis='...',
+ **kwargs):
+ cwd = self.get_shortened_path(pl, segment_info, **kwargs)
+ cwd_split = cwd.split(os.sep)
+ cwd_split_len = len(cwd_split)
+ cwd = [i[0:dir_shorten_len] if dir_shorten_len and i else i for i in cwd_split[:-1]] + [cwd_split[-1]]
+ if dir_limit_depth and cwd_split_len > dir_limit_depth + 1:
+ del(cwd[0:-dir_limit_depth])
+ if ellipsis is not None:
+ cwd.insert(0, ellipsis)
+ ret = []
+ if not cwd[0]:
+ cwd[0] = '/'
+ draw_inner_divider = not use_path_separator
+ for part in cwd:
+ if not part:
+ continue
+ if use_path_separator:
+ part += os.sep
+ ret.append({
+ 'contents': part,
+ 'divider_highlight_group': 'cwd:divider',
+ 'draw_inner_divider': draw_inner_divider,
+ })
+ ret[-1]['highlight_groups'] = ['cwd:current_folder', 'cwd']
+ if use_path_separator:
+ ret[-1]['contents'] = ret[-1]['contents'][:-1]
+ if len(ret) > 1 and ret[0]['contents'][0] == os.sep:
+ ret[0]['contents'] = ret[0]['contents'][1:]
+ return ret
+
+
+cwd = with_docstring(CwdSegment(),
+'''Return the current working directory.
+
+Returns a segment list to create a breadcrumb-like effect.
+
+:param int dir_shorten_len:
+ shorten parent directory names to this length (e.g.
+ :file:`/long/path/to/powerline` → :file:`/l/p/t/powerline`)
+:param int dir_limit_depth:
+ limit directory depth to this number (e.g.
+ :file:`/long/path/to/powerline` → :file:`⋯/to/powerline`)
+:param bool use_path_separator:
+ Use path separator in place of soft divider.
+:param bool shorten_home:
+ Shorten home directory to ``~``.
+:param str ellipsis:
+ Specifies what to use in place of omitted directories. Use None to not
+ show this subsegment at all.
+
+Divider highlight group used: ``cwd:divider``.
+
+Highlight groups used: ``cwd:current_folder`` or ``cwd``. It is recommended to define all highlight groups.
+''')
+
+
+try:
+ import psutil
+
+ # psutil-2.0.0: psutil.Process.username is unbound method
+ if callable(psutil.Process.username):
+ def _get_user():
+ return psutil.Process(os.getpid()).username()
+ # pre psutil-2.0.0: psutil.Process.username has type property
+ else:
+ def _get_user():
+ return psutil.Process(os.getpid()).username
+except ImportError:
+ try:
+ import pwd
+ except ImportError:
+ from getpass import getuser as _get_user
+ else:
+ try:
+ from os import geteuid as getuid
+ except ImportError:
+ from os import getuid
+
+ def _get_user():
+ return pwd.getpwuid(getuid()).pw_name
+
+
+username = False
+# os.geteuid is not available on windows
+_geteuid = getattr(os, 'geteuid', lambda: 1)
+
+
+@requires_segment_info
+def user(pl, segment_info, hide_user=None, hide_domain=False):
+ '''Return the current user.
+
+ :param str hide_user:
+ Omit showing segment for users with names equal to this string.
+ :param bool hide_domain:
+ Drop domain component if it exists in a username (delimited by '@').
+
+ Highlights the user with the ``superuser`` if the effective user ID is 0.
+
+ Highlight groups used: ``superuser`` or ``user``. It is recommended to define all highlight groups.
+ '''
+ global username
+ if (
+ segment_info['environ'].get('_POWERLINE_RUNNING_SHELL_TESTS')
+ == 'ee5bcdc6-b749-11e7-9456-50465d597777'
+ ):
+ return 'user'
+ if username is False:
+ username = _get_user()
+ if username is None:
+ pl.warn('Failed to get username')
+ return None
+ if username == hide_user:
+ return None
+ if hide_domain:
+ try:
+ username = username[:username.index('@')]
+ except ValueError:
+ pass
+ euid = _geteuid()
+ return [{
+ 'contents': username,
+ 'highlight_groups': ['user'] if euid != 0 else ['superuser', 'user'],
+ }]
diff --git a/powerline/segments/common/mail.py b/powerline/segments/common/mail.py
new file mode 100644
index 0000000..8202492
--- /dev/null
+++ b/powerline/segments/common/mail.py
@@ -0,0 +1,78 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import re
+
+from imaplib import IMAP4_SSL_PORT, IMAP4_SSL, IMAP4
+from collections import namedtuple
+
+from powerline.lib.threaded import KwThreadedSegment
+from powerline.segments import with_docstring
+
+
+_IMAPKey = namedtuple('Key', 'username password server port folder use_ssl')
+
+
+class EmailIMAPSegment(KwThreadedSegment):
+ interval = 60
+
+ @staticmethod
+ def key(username, password, server='imap.gmail.com', port=IMAP4_SSL_PORT, folder='INBOX', use_ssl=None, **kwargs):
+ if use_ssl is None:
+ use_ssl = (port == IMAP4_SSL_PORT)
+ return _IMAPKey(username, password, server, port, folder, use_ssl)
+
+ def compute_state(self, key):
+ if not key.username or not key.password:
+ self.warn('Username and password are not configured')
+ return None
+ if key.use_ssl:
+ mail = IMAP4_SSL(key.server, key.port)
+ else:
+ mail = IMAP4(key.server, key.port)
+ mail.login(key.username, key.password)
+ rc, message = mail.status(key.folder, '(UNSEEN)')
+ unread_str = message[0].decode('utf-8')
+ unread_count = int(re.search('UNSEEN (\d+)', unread_str).group(1))
+ return unread_count
+
+ @staticmethod
+ def render_one(unread_count, max_msgs=None, **kwargs):
+ if not unread_count:
+ return None
+ elif type(unread_count) != int or not max_msgs:
+ return [{
+ 'contents': str(unread_count),
+ 'highlight_groups': ['email_alert'],
+ }]
+ else:
+ return [{
+ 'contents': str(unread_count),
+ 'highlight_groups': ['email_alert_gradient', 'email_alert'],
+ 'gradient_level': min(unread_count * 100.0 / max_msgs, 100),
+ }]
+
+
+email_imap_alert = with_docstring(EmailIMAPSegment(),
+('''Return unread e-mail count for IMAP servers.
+
+:param str username:
+ login username
+:param str password:
+ login password
+:param str server:
+ e-mail server
+:param int port:
+ e-mail server port
+:param str folder:
+ folder to check for e-mails
+:param int max_msgs:
+ Maximum number of messages. If there are more messages then max_msgs then it
+ will use gradient level equal to 100, otherwise gradient level is equal to
+ ``100 * msgs_num / max_msgs``. If not present gradient is not computed.
+:param bool use_ssl:
+ If ``True`` then use SSL connection. If ``False`` then do not use it.
+ Default is ``True`` if port is equal to {ssl_port} and ``False`` otherwise.
+
+Highlight groups used: ``email_alert_gradient`` (gradient), ``email_alert``.
+''').format(ssl_port=IMAP4_SSL_PORT))
diff --git a/powerline/segments/common/net.py b/powerline/segments/common/net.py
new file mode 100644
index 0000000..b5d9062
--- /dev/null
+++ b/powerline/segments/common/net.py
@@ -0,0 +1,315 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import re
+import os
+import socket
+
+from powerline.lib.url import urllib_read
+from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
+from powerline.lib.monotonic import monotonic
+from powerline.lib.humanize_bytes import humanize_bytes
+from powerline.segments import with_docstring
+from powerline.theme import requires_segment_info
+
+
+@requires_segment_info
+def hostname(pl, segment_info, only_if_ssh=False, exclude_domain=False):
+ '''Return the current hostname.
+
+ :param bool only_if_ssh:
+ only return the hostname if currently in an SSH session
+ :param bool exclude_domain:
+ return the hostname without domain if there is one
+ '''
+ if (
+ segment_info['environ'].get('_POWERLINE_RUNNING_SHELL_TESTS')
+ == 'ee5bcdc6-b749-11e7-9456-50465d597777'
+ ):
+ return 'hostname'
+ if only_if_ssh and not segment_info['environ'].get('SSH_CLIENT'):
+ return None
+ if exclude_domain:
+ return socket.gethostname().split('.')[0]
+ return socket.gethostname()
+
+
+def _external_ip(query_url='http://ipv4.icanhazip.com/'):
+ return urllib_read(query_url).strip()
+
+
+class ExternalIpSegment(ThreadedSegment):
+ interval = 300
+
+ def set_state(self, query_url='http://ipv4.icanhazip.com/', **kwargs):
+ self.query_url = query_url
+ super(ExternalIpSegment, self).set_state(**kwargs)
+
+ def update(self, old_ip):
+ return _external_ip(query_url=self.query_url)
+
+ def render(self, ip, **kwargs):
+ if not ip:
+ return None
+ return [{'contents': ip, 'divider_highlight_group': 'background:divider'}]
+
+
+external_ip = with_docstring(ExternalIpSegment(),
+'''Return external IP address.
+
+:param str query_url:
+ URI to query for IP address, should return only the IP address as a text string
+
+ Suggested URIs:
+
+ * http://ipv4.icanhazip.com/
+ * http://ipv6.icanhazip.com/
+ * http://icanhazip.com/ (returns IPv6 address if available, else IPv4)
+
+Divider highlight group used: ``background:divider``.
+''')
+
+
+try:
+ import netifaces
+except ImportError:
+ def internal_ip(pl, interface='auto', ipv=4):
+ return None
+else:
+ _interface_starts = {
+ 'eth': 10, # Regular ethernet adapters : eth1
+ 'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0
+ 'en': 10, # OS X : en0
+ 'ath': 9, # Atheros WiFi adapters : ath0
+ 'wlan': 9, # Other WiFi adapters : wlan1
+ 'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0
+ 'teredo': 1, # miredo interface : teredo
+ 'lo': -10, # Loopback interface : lo
+ 'docker': -5, # Docker bridge interface : docker0
+ 'vmnet': -5, # VMWare bridge interface : vmnet1
+ 'vboxnet': -5, # VirtualBox bridge interface : vboxnet0
+ }
+
+ _interface_start_re = re.compile(r'^([a-z]+?)(\d|$)')
+
+ def _interface_key(interface):
+ match = _interface_start_re.match(interface)
+ if match:
+ try:
+ base = _interface_starts[match.group(1)] * 100
+ except KeyError:
+ base = 500
+ if match.group(2):
+ return base - int(match.group(2))
+ else:
+ return base
+ else:
+ return 0
+
+ def internal_ip(pl, interface='auto', ipv=4):
+ family = netifaces.AF_INET6 if ipv == 6 else netifaces.AF_INET
+ if interface == 'auto':
+ try:
+ interface = next(iter(sorted(netifaces.interfaces(), key=_interface_key, reverse=True)))
+ except StopIteration:
+ pl.info('No network interfaces found')
+ return None
+ elif interface == 'default_gateway':
+ try:
+ interface = netifaces.gateways()['default'][family][1]
+ except KeyError:
+ pl.info('No default gateway found for IPv{0}', ipv)
+ return None
+ addrs = netifaces.ifaddresses(interface)
+ try:
+ return addrs[family][0]['addr']
+ except (KeyError, IndexError):
+ pl.info("No IPv{0} address found for interface {1}", ipv, interface)
+ return None
+
+
+internal_ip = with_docstring(internal_ip,
+'''Return internal IP address
+
+Requires ``netifaces`` module to work properly.
+
+:param str interface:
+ Interface on which IP will be checked. Use ``auto`` to automatically
+ detect interface. In this case interfaces with lower numbers will be
+ preferred over interfaces with similar names. Order of preference based on
+ names:
+
+ #. ``eth`` and ``enp`` followed by number or the end of string.
+ #. ``ath``, ``wlan`` and ``wlp`` followed by number or the end of string.
+ #. ``teredo`` followed by number or the end of string.
+ #. Any other interface that is not ``lo*``.
+ #. ``lo`` followed by number or the end of string.
+
+ Use ``default_gateway`` to detect the interface based on the machine's
+ `default gateway <https://en.wikipedia.org/wiki/Default_gateway>`_ (i.e.,
+ the router to which it is connected).
+
+:param int ipv:
+ 4 or 6 for ipv4 and ipv6 respectively, depending on which IP address you
+ need exactly.
+''')
+
+
+try:
+ import psutil
+
+ def _get_bytes(interface):
+ try:
+ io_counters = psutil.net_io_counters(pernic=True)
+ except AttributeError:
+ io_counters = psutil.network_io_counters(pernic=True)
+ if_io = io_counters.get(interface)
+ if not if_io:
+ return None
+ return if_io.bytes_recv, if_io.bytes_sent
+
+ def _get_interfaces():
+ try:
+ io_counters = psutil.net_io_counters(pernic=True)
+ except AttributeError:
+ io_counters = psutil.network_io_counters(pernic=True)
+ for interface, data in io_counters.items():
+ if data:
+ yield interface, data.bytes_recv, data.bytes_sent
+except ImportError:
+ def _get_bytes(interface):
+ with open('/sys/class/net/{interface}/statistics/rx_bytes'.format(interface=interface), 'rb') as file_obj:
+ rx = int(file_obj.read())
+ with open('/sys/class/net/{interface}/statistics/tx_bytes'.format(interface=interface), 'rb') as file_obj:
+ tx = int(file_obj.read())
+ return (rx, tx)
+
+ def _get_interfaces():
+ for interface in os.listdir('/sys/class/net'):
+ x = _get_bytes(interface)
+ if x is not None:
+ yield interface, x[0], x[1]
+
+
+class NetworkLoadSegment(KwThreadedSegment):
+ interfaces = {}
+ replace_num_pat = re.compile(r'[a-zA-Z]+')
+
+ @staticmethod
+ def key(interface='auto', **kwargs):
+ return interface
+
+ def compute_state(self, interface):
+ if interface == 'auto':
+ proc_exists = getattr(self, 'proc_exists', None)
+ if proc_exists is None:
+ proc_exists = self.proc_exists = os.path.exists('/proc/net/route')
+ if proc_exists:
+ # Look for default interface in routing table
+ with open('/proc/net/route', 'rb') as f:
+ for line in f.readlines():
+ parts = line.split()
+ if len(parts) > 1:
+ iface, destination = parts[:2]
+ if not destination.replace(b'0', b''):
+ interface = iface.decode('utf-8')
+ break
+ if interface == 'auto':
+ # Choose interface with most total activity, excluding some
+ # well known interface names
+ interface, total = 'eth0', -1
+ for name, rx, tx in _get_interfaces():
+ base = self.replace_num_pat.match(name)
+ if None in (base, rx, tx) or base.group() in ('lo', 'vmnet', 'sit'):
+ continue
+ activity = rx + tx
+ if activity > total:
+ total = activity
+ interface = name
+
+ try:
+ idata = self.interfaces[interface]
+ try:
+ idata['prev'] = idata['last']
+ except KeyError:
+ pass
+ except KeyError:
+ idata = {}
+ if self.run_once:
+ idata['prev'] = (monotonic(), _get_bytes(interface))
+ self.shutdown_event.wait(self.interval)
+ self.interfaces[interface] = idata
+
+ idata['last'] = (monotonic(), _get_bytes(interface))
+ return idata.copy()
+
+ def render_one(self, idata, recv_format='DL {value:>8}', sent_format='UL {value:>8}', suffix='B/s', si_prefix=False, **kwargs):
+ if not idata or 'prev' not in idata:
+ return None
+
+ t1, b1 = idata['prev']
+ t2, b2 = idata['last']
+ measure_interval = t2 - t1
+
+ if None in (b1, b2):
+ return None
+
+ r = []
+ for i, key in zip((0, 1), ('recv', 'sent')):
+ format = locals()[key + '_format']
+ try:
+ value = (b2[i] - b1[i]) / measure_interval
+ except ZeroDivisionError:
+ self.warn('Measure interval zero.')
+ value = 0
+ max_key = key + '_max'
+ is_gradient = max_key in kwargs
+ hl_groups = ['network_load_' + key, 'network_load']
+ if is_gradient:
+ hl_groups[:0] = (group + '_gradient' for group in hl_groups)
+ r.append({
+ 'contents': format.format(value=humanize_bytes(value, suffix, si_prefix)),
+ 'divider_highlight_group': 'network_load:divider',
+ 'highlight_groups': hl_groups,
+ })
+ if is_gradient:
+ max = kwargs[max_key]
+ if value >= max:
+ r[-1]['gradient_level'] = 100
+ else:
+ r[-1]['gradient_level'] = value * 100.0 / max
+
+ return r
+
+
+network_load = with_docstring(NetworkLoadSegment(),
+'''Return the network load.
+
+Uses the ``psutil`` module if available for multi-platform compatibility,
+falls back to reading
+:file:`/sys/class/net/{interface}/statistics/{rx,tx}_bytes`.
+
+:param str interface:
+ Network interface to measure (use the special value "auto" to have powerline
+ try to auto-detect the network interface).
+:param str suffix:
+ String appended to each load string.
+:param bool si_prefix:
+ Use SI prefix, e.g. MB instead of MiB.
+:param str recv_format:
+ Format string that determines how download speed should look like. Receives
+ ``value`` as argument.
+:param str sent_format:
+ Format string that determines how upload speed should look like. Receives
+ ``value`` as argument.
+:param float recv_max:
+ Maximum number of received bytes per second. Is only used to compute
+ gradient level.
+:param float sent_max:
+ Maximum number of sent bytes per second. Is only used to compute gradient
+ level.
+
+Divider highlight group used: ``network_load:divider``.
+
+Highlight groups used: ``network_load_sent_gradient`` (gradient) or ``network_load_recv_gradient`` (gradient) or ``network_load_gradient`` (gradient), ``network_load_sent`` or ``network_load_recv`` or ``network_load``.
+''')
diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py
new file mode 100644
index 0000000..f43db0c
--- /dev/null
+++ b/powerline/segments/common/players.py
@@ -0,0 +1,636 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import re
+
+from powerline.lib.shell import asrun, run_cmd
+from powerline.lib.unicode import out_u
+from powerline.segments import Segment, with_docstring
+
+
+STATE_SYMBOLS = {
+ 'fallback': '',
+ 'play': '>',
+ 'pause': '~',
+ 'stop': 'X',
+}
+
+
+def _convert_state(state):
+ '''Guess player state'''
+ state = state.lower()
+ if 'play' in state:
+ return 'play'
+ if 'pause' in state:
+ return 'pause'
+ if 'stop' in state:
+ return 'stop'
+ return 'fallback'
+
+
+def _convert_seconds(seconds):
+ '''Convert seconds to minutes:seconds format'''
+ if isinstance(seconds, str):
+ seconds = seconds.replace(",",".")
+ return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60))
+
+
+class PlayerSegment(Segment):
+ def __call__(self, format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs):
+ stats = {
+ 'state': 'fallback',
+ 'album': None,
+ 'artist': None,
+ 'title': None,
+ 'elapsed': None,
+ 'total': None,
+ }
+ func_stats = self.get_player_status(**kwargs)
+ if not func_stats:
+ return None
+ stats.update(func_stats)
+ stats['state_symbol'] = state_symbols.get(stats['state'])
+ return [{
+ 'contents': format.format(**stats),
+ 'highlight_groups': ['player_' + (stats['state'] or 'fallback'), 'player'],
+ }]
+
+ def get_player_status(self, pl):
+ pass
+
+ def argspecobjs(self):
+ for ret in super(PlayerSegment, self).argspecobjs():
+ yield ret
+ yield 'get_player_status', self.get_player_status
+
+ def omitted_args(self, name, method):
+ return ()
+
+
+_common_args = '''
+This player segment should be added like this:
+
+.. code-block:: json
+
+ {{
+ "function": "powerline.segments.common.players.{0}",
+ "name": "player"
+ }}
+
+(with additional ``"args": {{…}}`` if needed).
+
+Highlight groups used: ``player_fallback`` or ``player``, ``player_play`` or ``player``, ``player_pause`` or ``player``, ``player_stop`` or ``player``.
+
+:param str format:
+ Format used for displaying data from player. Should be a str.format-like
+ string with the following keyword parameters:
+
+ +------------+-------------------------------------------------------------+
+ |Parameter |Description |
+ +============+=============================================================+
+ |state_symbol|Symbol displayed for play/pause/stop states. There is also |
+ | |“fallback” state used in case function failed to get player |
+ | |state. For this state symbol is by default empty. All |
+ | |symbols are defined in ``state_symbols`` argument. |
+ +------------+-------------------------------------------------------------+
+ |album |Album that is currently played. |
+ +------------+-------------------------------------------------------------+
+ |artist |Artist whose song is currently played |
+ +------------+-------------------------------------------------------------+
+ |title |Currently played composition. |
+ +------------+-------------------------------------------------------------+
+ |elapsed |Composition duration in format M:SS (minutes:seconds). |
+ +------------+-------------------------------------------------------------+
+ |total |Composition length in format M:SS. |
+ +------------+-------------------------------------------------------------+
+:param dict state_symbols:
+ Symbols used for displaying state. Must contain all of the following keys:
+
+ ======== ========================================================
+ Key Description
+ ======== ========================================================
+ play Displayed when player is playing.
+ pause Displayed when player is paused.
+ stop Displayed when player is not playing anything.
+ fallback Displayed if state is not one of the above or not known.
+ ======== ========================================================
+'''
+
+
+_player = with_docstring(PlayerSegment(), _common_args.format('_player'))
+
+
+class CmusPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ '''Return cmus player information.
+
+ cmus-remote -Q returns data with multi-level information i.e.
+ status playing
+ file <file_name>
+ tag artist <artist_name>
+ tag title <track_title>
+ tag ..
+ tag n
+ set continue <true|false>
+ set repeat <true|false>
+ set ..
+ set n
+
+ For the information we are looking for we don’t really care if we’re on
+ the tag level or the set level. The dictionary comprehension in this
+ method takes anything in ignore_levels and brings the key inside that
+ to the first level of the dictionary.
+ '''
+ now_playing_str = run_cmd(pl, ['cmus-remote', '-Q'])
+ if not now_playing_str:
+ return
+ ignore_levels = ('tag', 'set',)
+ now_playing = dict(((token[0] if token[0] not in ignore_levels else token[1],
+ (' '.join(token[1:]) if token[0] not in ignore_levels else
+ ' '.join(token[2:]))) for token in [line.split(' ') for line in now_playing_str.split('\n')[:-1]]))
+ state = _convert_state(now_playing.get('status'))
+ return {
+ 'state': state,
+ 'album': now_playing.get('album'),
+ 'artist': now_playing.get('artist'),
+ 'title': now_playing.get('title'),
+ 'elapsed': _convert_seconds(now_playing.get('position', 0)),
+ 'total': _convert_seconds(now_playing.get('duration', 0)),
+ }
+
+
+cmus = with_docstring(CmusPlayerSegment(),
+('''Return CMUS player information
+
+Requires cmus-remote command be accessible from $PATH.
+
+{0}
+''').format(_common_args.format('cmus')))
+
+
+class MpdPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl, host='localhost', password=None, port=6600):
+ try:
+ import mpd
+ except ImportError:
+ if password:
+ host = password + '@' + host
+ now_playing = run_cmd(pl, [
+ 'mpc',
+ '-h', host,
+ '-p', str(port)
+ ], strip=False)
+ album = run_cmd(pl, [
+ 'mpc', 'current',
+ '-f', '%album%',
+ '-h', host,
+ '-p', str(port)
+ ])
+ if not now_playing or now_playing.count("\n") != 3:
+ return
+ now_playing = re.match(
+ r"(.*) - (.*)\n\[([a-z]+)\] +[#0-9\/]+ +([0-9\:]+)\/([0-9\:]+)",
+ now_playing
+ )
+ return {
+ 'state': _convert_state(now_playing[3]),
+ 'album': album,
+ 'artist': now_playing[1],
+ 'title': now_playing[2],
+ 'elapsed': now_playing[4],
+ 'total': now_playing[5]
+ }
+ else:
+ try:
+ client = mpd.MPDClient(use_unicode=True)
+ except TypeError:
+ # python-mpd 1.x does not support use_unicode
+ client = mpd.MPDClient()
+ client.connect(host, port)
+ if password:
+ client.password(password)
+ now_playing = client.currentsong()
+ if not now_playing:
+ return
+ status = client.status()
+ client.close()
+ client.disconnect()
+ return {
+ 'state': status.get('state'),
+ 'album': now_playing.get('album'),
+ 'artist': now_playing.get('artist'),
+ 'title': now_playing.get('title'),
+ 'elapsed': _convert_seconds(status.get('elapsed', 0)),
+ 'total': _convert_seconds(now_playing.get('time', 0)),
+ }
+
+
+mpd = with_docstring(MpdPlayerSegment(),
+('''Return Music Player Daemon information
+
+Requires ``mpd`` Python module (e.g. |python-mpd2|_ or |python-mpd|_ Python
+package) or alternatively the ``mpc`` command to be accessible from $PATH.
+
+.. |python-mpd| replace:: ``python-mpd``
+.. _python-mpd: https://pypi.python.org/pypi/python-mpd
+
+.. |python-mpd2| replace:: ``python-mpd2``
+.. _python-mpd2: https://pypi.python.org/pypi/python-mpd2
+
+{0}
+:param str host:
+ Host on which mpd runs.
+:param str password:
+ Password used for connecting to daemon.
+:param int port:
+ Port which should be connected to.
+''').format(_common_args.format('mpd')))
+
+
+try:
+ import dbus
+except ImportError:
+ def _get_dbus_player_status(pl, player_name, **kwargs):
+ pl.error('Could not add {0} segment: requires dbus module', player_name)
+ return
+else:
+ def _get_dbus_player_status(pl,
+ bus_name=None,
+ iface_prop='org.freedesktop.DBus.Properties',
+ iface_player='org.mpris.MediaPlayer2.Player',
+ player_path='/org/mpris/MediaPlayer2',
+ player_name='player'):
+ bus = dbus.SessionBus()
+
+ if bus_name is None:
+ for service in bus.list_names():
+ if re.match('org.mpris.MediaPlayer2.', service):
+ bus_name = service
+ break
+
+ try:
+ player = bus.get_object(bus_name, player_path)
+ iface = dbus.Interface(player, iface_prop)
+ info = iface.Get(iface_player, 'Metadata')
+ status = iface.Get(iface_player, 'PlaybackStatus')
+ except dbus.exceptions.DBusException:
+ return
+ if not info:
+ return
+
+ try:
+ elapsed = iface.Get(iface_player, 'Position')
+ except dbus.exceptions.DBusException:
+ pl.warning('Missing player elapsed time')
+ elapsed = None
+ else:
+ elapsed = _convert_seconds(elapsed / 1e6)
+ album = info.get('xesam:album')
+ title = info.get('xesam:title')
+ artist = info.get('xesam:artist')
+ state = _convert_state(status)
+ if album:
+ album = out_u(album)
+ if title:
+ title = out_u(title)
+ if artist:
+ artist = out_u(artist[0])
+
+ length = info.get('mpris:length')
+ # avoid parsing `None` length values, that would
+ # raise an error otherwise
+ parsed_length = length and _convert_seconds(length / 1e6)
+
+ return {
+ 'state': state,
+ 'album': album,
+ 'artist': artist,
+ 'title': title,
+ 'elapsed': elapsed,
+ 'total': parsed_length,
+ }
+
+
+class DbusPlayerSegment(PlayerSegment):
+ get_player_status = staticmethod(_get_dbus_player_status)
+
+
+dbus_player = with_docstring(DbusPlayerSegment(),
+('''Return generic dbus player state
+
+Requires ``dbus`` python module. Only for players that support specific protocol
+ (e.g. like :py:func:`spotify` and :py:func:`clementine`).
+
+{0}
+:param str player_name:
+ Player name. Used in error messages only.
+:param str bus_name:
+ Dbus bus name.
+:param str player_path:
+ Path to the player on the given bus.
+:param str iface_prop:
+ Interface properties name for use with dbus.Interface.
+:param str iface_player:
+ Player name.
+''').format(_common_args.format('dbus_player')))
+
+
+class SpotifyDbusPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ player_status = _get_dbus_player_status(
+ pl=pl,
+ player_name='Spotify',
+ bus_name='org.mpris.MediaPlayer2.spotify',
+ player_path='/org/mpris/MediaPlayer2',
+ iface_prop='org.freedesktop.DBus.Properties',
+ iface_player='org.mpris.MediaPlayer2.Player',
+ )
+ if player_status is not None:
+ return player_status
+ # Fallback for legacy spotify client with different DBus protocol
+ return _get_dbus_player_status(
+ pl=pl,
+ player_name='Spotify',
+ bus_name='com.spotify.qt',
+ player_path='/',
+ iface_prop='org.freedesktop.DBus.Properties',
+ iface_player='org.freedesktop.MediaPlayer2',
+ )
+
+
+spotify_dbus = with_docstring(SpotifyDbusPlayerSegment(),
+('''Return spotify player information
+
+Requires ``dbus`` python module.
+
+{0}
+''').format(_common_args.format('spotify_dbus')))
+
+
+class SpotifyAppleScriptPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ status_delimiter = '-~`/='
+ ascript = '''
+ tell application "System Events"
+ set process_list to (name of every process)
+ end tell
+
+ if process_list contains "Spotify" then
+ tell application "Spotify"
+ if player state is playing or player state is paused then
+ set track_name to name of current track
+ set artist_name to artist of current track
+ set album_name to album of current track
+ set track_length to duration of current track
+ set now_playing to "" & player state & "{0}" & album_name & "{0}" & artist_name & "{0}" & track_name & "{0}" & track_length & "{0}" & player position
+ return now_playing
+ else
+ return player state
+ end if
+
+ end tell
+ else
+ return "stopped"
+ end if
+ '''.format(status_delimiter)
+
+ spotify = asrun(pl, ascript)
+ if not asrun:
+ return None
+
+ spotify_status = spotify.split(status_delimiter)
+ state = _convert_state(spotify_status[0])
+ if state == 'stop':
+ return None
+ return {
+ 'state': state,
+ 'album': spotify_status[1],
+ 'artist': spotify_status[2],
+ 'title': spotify_status[3],
+ 'total': _convert_seconds(int(spotify_status[4])/1000),
+ 'elapsed': _convert_seconds(spotify_status[5]),
+ }
+
+
+spotify_apple_script = with_docstring(SpotifyAppleScriptPlayerSegment(),
+('''Return spotify player information
+
+Requires ``osascript`` available in $PATH.
+
+{0}
+''').format(_common_args.format('spotify_apple_script')))
+
+
+if not sys.platform.startswith('darwin'):
+ spotify = spotify_dbus
+ _old_name = 'spotify_dbus'
+else:
+ spotify = spotify_apple_script
+ _old_name = 'spotify_apple_script'
+
+
+spotify = with_docstring(spotify, spotify.__doc__.replace(_old_name, 'spotify'))
+
+
+class ClementinePlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ return _get_dbus_player_status(
+ pl=pl,
+ player_name='Clementine',
+ bus_name='org.mpris.MediaPlayer2.clementine',
+ player_path='/org/mpris/MediaPlayer2',
+ iface_prop='org.freedesktop.DBus.Properties',
+ iface_player='org.mpris.MediaPlayer2.Player',
+ )
+
+
+clementine = with_docstring(ClementinePlayerSegment(),
+('''Return clementine player information
+
+Requires ``dbus`` python module.
+
+{0}
+''').format(_common_args.format('clementine')))
+
+
+class RhythmboxPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ now_playing = run_cmd(pl, [
+ 'rhythmbox-client',
+ '--no-start', '--no-present',
+ '--print-playing-format', '%at\n%aa\n%tt\n%te\n%td'
+ ], strip=False)
+ if not now_playing:
+ return
+ now_playing = now_playing.split('\n')
+ return {
+ 'album': now_playing[0],
+ 'artist': now_playing[1],
+ 'title': now_playing[2],
+ 'elapsed': now_playing[3],
+ 'total': now_playing[4],
+ }
+
+
+rhythmbox = with_docstring(RhythmboxPlayerSegment(),
+('''Return rhythmbox player information
+
+Requires ``rhythmbox-client`` available in $PATH.
+
+{0}
+''').format(_common_args.format('rhythmbox')))
+
+
+class RDIOPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ status_delimiter = '-~`/='
+ ascript = '''
+ tell application "System Events"
+ set rdio_active to the count(every process whose name is "Rdio")
+ if rdio_active is 0 then
+ return
+ end if
+ end tell
+ tell application "Rdio"
+ set rdio_name to the name of the current track
+ set rdio_artist to the artist of the current track
+ set rdio_album to the album of the current track
+ set rdio_duration to the duration of the current track
+ set rdio_state to the player state
+ set rdio_elapsed to the player position
+ return rdio_name & "{0}" & rdio_artist & "{0}" & rdio_album & "{0}" & rdio_elapsed & "{0}" & rdio_duration & "{0}" & rdio_state
+ end tell
+ '''.format(status_delimiter)
+ now_playing = asrun(pl, ascript)
+ if not now_playing:
+ return
+ now_playing = now_playing.split(status_delimiter)
+ if len(now_playing) != 6:
+ return
+ state = _convert_state(now_playing[5])
+ total = _convert_seconds(now_playing[4])
+ elapsed = _convert_seconds(float(now_playing[3]) * float(now_playing[4]) / 100)
+ return {
+ 'title': now_playing[0],
+ 'artist': now_playing[1],
+ 'album': now_playing[2],
+ 'elapsed': elapsed,
+ 'total': total,
+ 'state': state,
+ }
+
+
+rdio = with_docstring(RDIOPlayerSegment(),
+('''Return rdio player information
+
+Requires ``osascript`` available in $PATH.
+
+{0}
+''').format(_common_args.format('rdio')))
+
+
+class ITunesPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ status_delimiter = '-~`/='
+ ascript = '''
+ tell application "System Events"
+ set process_list to (name of every process)
+ end tell
+
+ if process_list contains "iTunes" then
+ tell application "iTunes"
+ if player state is playing then
+ set t_title to name of current track
+ set t_artist to artist of current track
+ set t_album to album of current track
+ set t_duration to duration of current track
+ set t_elapsed to player position
+ set t_state to player state
+ return t_title & "{0}" & t_artist & "{0}" & t_album & "{0}" & t_elapsed & "{0}" & t_duration & "{0}" & t_state
+ end if
+ end tell
+ end if
+ '''.format(status_delimiter)
+ now_playing = asrun(pl, ascript)
+ if not now_playing:
+ return
+ now_playing = now_playing.split(status_delimiter)
+ if len(now_playing) != 6:
+ return
+ title, artist, album = now_playing[0], now_playing[1], now_playing[2]
+ state = _convert_state(now_playing[5])
+ total = _convert_seconds(now_playing[4])
+ elapsed = _convert_seconds(now_playing[3])
+ return {
+ 'title': title,
+ 'artist': artist,
+ 'album': album,
+ 'total': total,
+ 'elapsed': elapsed,
+ 'state': state
+ }
+
+
+itunes = with_docstring(ITunesPlayerSegment(),
+('''Return iTunes now playing information
+
+Requires ``osascript``.
+
+{0}
+''').format(_common_args.format('itunes')))
+
+
+class MocPlayerSegment(PlayerSegment):
+ def get_player_status(self, pl):
+ '''Return Music On Console (mocp) player information.
+
+ ``mocp -i`` returns current information i.e.
+
+ .. code-block::
+
+ File: filename.format
+ Title: full title
+ Artist: artist name
+ SongTitle: song title
+ Album: album name
+ TotalTime: 00:00
+ TimeLeft: 00:00
+ TotalSec: 000
+ CurrentTime: 00:00
+ CurrentSec: 000
+ Bitrate: 000kbps
+ AvgBitrate: 000kbps
+ Rate: 00kHz
+
+ For the information we are looking for we don’t really care if we have
+ extra-timing information or bit rate level. The dictionary comprehension
+ in this method takes anything in ignore_info and brings the key inside
+ that to the right info of the dictionary.
+ '''
+ now_playing_str = run_cmd(pl, ['mocp', '-i'])
+ if not now_playing_str:
+ return
+
+ now_playing = dict((
+ line.split(': ', 1)
+ for line in now_playing_str.split('\n')[:-1]
+ ))
+ state = _convert_state(now_playing.get('State', 'stop'))
+ return {
+ 'state': state,
+ 'album': now_playing.get('Album', ''),
+ 'artist': now_playing.get('Artist', ''),
+ 'title': now_playing.get('SongTitle', ''),
+ 'elapsed': _convert_seconds(now_playing.get('CurrentSec', 0)),
+ 'total': _convert_seconds(now_playing.get('TotalSec', 0)),
+ }
+
+
+mocp = with_docstring(MocPlayerSegment(),
+('''Return MOC (Music On Console) player information
+
+Requires version >= 2.3.0 and ``mocp`` executable in ``$PATH``.
+
+{0}
+''').format(_common_args.format('mocp')))
diff --git a/powerline/segments/common/sys.py b/powerline/segments/common/sys.py
new file mode 100644
index 0000000..29a2459
--- /dev/null
+++ b/powerline/segments/common/sys.py
@@ -0,0 +1,184 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from multiprocessing import cpu_count as _cpu_count
+
+from powerline.lib.threaded import ThreadedSegment
+from powerline.lib import add_divider_highlight_group
+from powerline.segments import with_docstring
+
+
+cpu_count = None
+
+
+def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2,
+ track_cpu_count=False, short=False):
+ '''Return system load average.
+
+ Highlights using ``system_load_good``, ``system_load_bad`` and
+ ``system_load_ugly`` highlighting groups, depending on the thresholds
+ passed to the function.
+
+ :param str format:
+ format string, receives ``avg`` as an argument
+ :param float threshold_good:
+ threshold for gradient level 0: any normalized load average below this
+ value will have this gradient level.
+ :param float threshold_bad:
+ threshold for gradient level 100: any normalized load average above this
+ value will have this gradient level. Load averages between
+ ``threshold_good`` and ``threshold_bad`` receive gradient level that
+ indicates relative position in this interval:
+ (``100 * (cur-good) / (bad-good)``).
+ Note: both parameters are checked against normalized load averages.
+ :param bool track_cpu_count:
+ if True powerline will continuously poll the system to detect changes
+ in the number of CPUs.
+ :param bool short:
+ if True only the sys load over last 1 minute will be displayed.
+
+ Divider highlight group used: ``background:divider``.
+
+ Highlight groups used: ``system_load_gradient`` (gradient) or ``system_load``.
+ '''
+ global cpu_count
+ try:
+ cpu_num = cpu_count = _cpu_count() if cpu_count is None or track_cpu_count else cpu_count
+ except NotImplementedError:
+ pl.warn('Unable to get CPU count: method is not implemented')
+ return None
+ ret = []
+ for avg in os.getloadavg():
+ normalized = avg / cpu_num
+ if normalized < threshold_good:
+ gradient_level = 0
+ elif normalized < threshold_bad:
+ gradient_level = (normalized - threshold_good) * 100.0 / (threshold_bad - threshold_good)
+ else:
+ gradient_level = 100
+ ret.append({
+ 'contents': format.format(avg=avg),
+ 'highlight_groups': ['system_load_gradient', 'system_load'],
+ 'divider_highlight_group': 'background:divider',
+ 'gradient_level': gradient_level,
+ })
+
+ if short:
+ return ret
+
+ ret[0]['contents'] += ' '
+ ret[1]['contents'] += ' '
+ return ret
+
+
+try:
+ import psutil
+
+ class CPULoadPercentSegment(ThreadedSegment):
+ interval = 1
+
+ def update(self, old_cpu):
+ return psutil.cpu_percent(interval=None)
+
+ def run(self):
+ while not self.shutdown_event.is_set():
+ try:
+ self.update_value = psutil.cpu_percent(interval=self.interval)
+ except Exception as e:
+ self.exception('Exception while calculating cpu_percent: {0}', str(e))
+
+ def render(self, cpu_percent, format='{0:.0f}%', **kwargs):
+ return [{
+ 'contents': format.format(cpu_percent),
+ 'gradient_level': cpu_percent,
+ 'highlight_groups': ['cpu_load_percent_gradient', 'cpu_load_percent'],
+ }]
+except ImportError:
+ class CPULoadPercentSegment(ThreadedSegment):
+ interval = 1
+
+ @staticmethod
+ def startup(**kwargs):
+ pass
+
+ @staticmethod
+ def start():
+ pass
+
+ @staticmethod
+ def shutdown():
+ pass
+
+ @staticmethod
+ def render(cpu_percent, pl, format='{0:.0f}%', **kwargs):
+ pl.warn('Module “psutil” is not installed, thus CPU load is not available')
+ return None
+
+
+cpu_load_percent = with_docstring(CPULoadPercentSegment(),
+'''Return the average CPU load as a percentage.
+
+Requires the ``psutil`` module.
+
+:param str format:
+ Output format. Accepts measured CPU load as the first argument.
+
+Highlight groups used: ``cpu_load_percent_gradient`` (gradient) or ``cpu_load_percent``.
+''')
+
+
+if os.path.exists('/proc/uptime'):
+ def _get_uptime():
+ with open('/proc/uptime', 'r') as f:
+ return int(float(f.readline().split()[0]))
+elif 'psutil' in globals():
+ from time import time
+
+ if hasattr(psutil, 'boot_time'):
+ def _get_uptime():
+ return int(time() - psutil.boot_time())
+ else:
+ def _get_uptime():
+ return int(time() - psutil.BOOT_TIME)
+else:
+ def _get_uptime():
+ raise NotImplementedError
+
+
+@add_divider_highlight_group('background:divider')
+def uptime(pl, days_format='{days:d}d', hours_format=' {hours:d}h', minutes_format=' {minutes:02d}m',
+ seconds_format=' {seconds:02d}s', shorten_len=3):
+ '''Return system uptime.
+
+ :param str days_format:
+ day format string, will be passed ``days`` as the argument
+ :param str hours_format:
+ hour format string, will be passed ``hours`` as the argument
+ :param str minutes_format:
+ minute format string, will be passed ``minutes`` as the argument
+ :param str seconds_format:
+ second format string, will be passed ``seconds`` as the argument
+ :param int shorten_len:
+ shorten the amount of units (days, hours, etc.) displayed
+
+ Divider highlight group used: ``background:divider``.
+ '''
+ try:
+ seconds = _get_uptime()
+ except NotImplementedError:
+ pl.warn('Unable to get uptime. You should install psutil module')
+ return None
+ minutes, seconds = divmod(seconds, 60)
+ hours, minutes = divmod(minutes, 60)
+ days, hours = divmod(hours, 24)
+ time_formatted = list(filter(None, [
+ days_format.format(days=days) if days_format else None,
+ hours_format.format(hours=hours) if hours_format else None,
+ minutes_format.format(minutes=minutes) if minutes_format else None,
+ seconds_format.format(seconds=seconds) if seconds_format else None,
+ ]))
+ first_non_zero = next((i for i, x in enumerate([days, hours, minutes, seconds]) if x != 0))
+ time_formatted = time_formatted[first_non_zero:first_non_zero + shorten_len]
+ return ''.join(time_formatted).strip()
diff --git a/powerline/segments/common/time.py b/powerline/segments/common/time.py
new file mode 100644
index 0000000..be727c9
--- /dev/null
+++ b/powerline/segments/common/time.py
@@ -0,0 +1,123 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from datetime import datetime
+
+
+def date(pl, format='%Y-%m-%d', istime=False, timezone=None):
+ '''Return the current date.
+
+ :param str format:
+ strftime-style date format string
+ :param bool istime:
+ If true then segment uses ``time`` highlight group.
+ :param string timezone:
+ Specify a timezone to use as ``+HHMM`` or ``-HHMM``.
+ (Defaults to system defaults.)
+
+ Divider highlight group used: ``time:divider``.
+
+ Highlight groups used: ``time`` or ``date``.
+ '''
+
+ try:
+ tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None
+ except ValueError:
+ tz = None
+
+ nw = datetime.now(tz)
+
+ try:
+ contents = nw.strftime(format)
+ except UnicodeEncodeError:
+ contents = nw.strftime(format.encode('utf-8')).decode('utf-8')
+
+ return [{
+ 'contents': contents,
+ 'highlight_groups': (['time'] if istime else []) + ['date'],
+ 'divider_highlight_group': 'time:divider' if istime else None,
+ }]
+
+
+UNICODE_TEXT_TRANSLATION = {
+ ord('\''): '’',
+ ord('-'): '‐',
+}
+
+
+def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezone=None, hour_str=['twelve', 'one', 'two', 'three', 'four',
+ 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven'], minute_str = {
+ '0': 'o\'clock', '5': 'five past', '10': 'ten past','15': 'quarter past',
+ '20': 'twenty past', '25': 'twenty-five past', '30': 'half past', '35': 'twenty-five to',
+ '40': 'twenty to', '45': 'quarter to', '50': 'ten to', '55': 'five to'
+ }, special_case_str = {
+ '(23, 58)': 'round about midnight',
+ '(23, 59)': 'round about midnight',
+ '(0, 0)': 'midnight',
+ '(0, 1)': 'round about midnight',
+ '(0, 2)': 'round about midnight',
+ '(12, 0)': 'noon',
+ }):
+
+ '''Display the current time as fuzzy time, e.g. "quarter past six".
+
+ :param string format:
+ Format used to display the fuzzy time. (Ignored when a special time
+ is displayed.)
+ :param bool unicode_text:
+ If true then hyphenminuses (regular ASCII ``-``) and single quotes are
+ replaced with unicode dashes and apostrophes.
+ :param string timezone:
+ Specify a timezone to use as ``+HHMM`` or ``-HHMM``.
+ (Defaults to system defaults.)
+ :param string list hour_str:
+ Strings to be used to display the hour, starting with midnight.
+ (This list may contain 12 or 24 entries.)
+ :param dict minute_str:
+ Dictionary mapping minutes to strings to be used to display them.
+ :param dict special_case_str:
+ Special strings for special times.
+
+ Highlight groups used: ``fuzzy_time``.
+ '''
+
+ try:
+ tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None
+ except ValueError:
+ tz = None
+
+ now = datetime.now(tz)
+
+ try:
+ # We don't want to enforce a special type of spaces/ alignment in the input
+ from ast import literal_eval
+ special_case_str = {literal_eval(x):special_case_str[x] for x in special_case_str}
+ result = special_case_str[(now.hour, now.minute)]
+ if unicode_text:
+ result = result.translate(UNICODE_TEXT_TRANSLATION)
+ return result
+ except KeyError:
+ pass
+
+ hour = now.hour
+ if now.minute >= 30:
+ hour = hour + 1
+ hour = hour % len(hour_str)
+
+ min_dis = 100
+ min_pos = 0
+
+ for mn in minute_str:
+ mn = int(mn)
+ if now.minute >= mn and now.minute - mn < min_dis:
+ min_dis = now.minute - mn
+ min_pos = mn
+ elif now.minute < mn and mn - now.minute < min_dis:
+ min_dis = mn - now.minute
+ min_pos = mn
+ result = format.format(minute_str=minute_str[str(min_pos)], hour_str=hour_str[hour])
+
+ if unicode_text:
+ result = result.translate(UNICODE_TEXT_TRANSLATION)
+
+ return result
diff --git a/powerline/segments/common/vcs.py b/powerline/segments/common/vcs.py
new file mode 100644
index 0000000..07679ae
--- /dev/null
+++ b/powerline/segments/common/vcs.py
@@ -0,0 +1,89 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.lib.vcs import guess, tree_status
+from powerline.segments import Segment, with_docstring
+from powerline.theme import requires_segment_info, requires_filesystem_watcher
+
+
+@requires_filesystem_watcher
+@requires_segment_info
+class BranchSegment(Segment):
+ divider_highlight_group = None
+
+ @staticmethod
+ def get_directory(segment_info):
+ return segment_info['getcwd']()
+
+ def __call__(self, pl, segment_info, create_watcher, status_colors=False, ignore_statuses=()):
+ name = self.get_directory(segment_info)
+ if name:
+ repo = guess(path=name, create_watcher=create_watcher)
+ if repo is not None:
+ branch = repo.branch()
+ scol = ['branch']
+ if status_colors:
+ try:
+ status = tree_status(repo, pl)
+ except Exception as e:
+ pl.exception('Failed to compute tree status: {0}', str(e))
+ status = '?'
+ else:
+ status = status and status.strip()
+ if status in ignore_statuses:
+ status = None
+ scol.insert(0, 'branch_dirty' if status else 'branch_clean')
+ return [{
+ 'contents': branch,
+ 'highlight_groups': scol,
+ 'divider_highlight_group': self.divider_highlight_group,
+ }]
+
+
+branch = with_docstring(BranchSegment(),
+'''Return the current VCS branch.
+
+:param bool status_colors:
+ Determines whether repository status will be used to determine highlighting.
+ Default: False.
+:param list ignore_statuses:
+ List of statuses which will not result in repo being marked as dirty. Most
+ useful is setting this option to ``["U"]``: this will ignore repository
+ which has just untracked files (i.e. repository with modified, deleted or
+ removed files will be marked as dirty, while just untracked files will make
+ segment show clean repository). Only applicable if ``status_colors`` option
+ is True.
+
+Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``.
+''')
+
+
+@requires_filesystem_watcher
+@requires_segment_info
+class StashSegment(Segment):
+ divider_highlight_group = None
+
+ @staticmethod
+ def get_directory(segment_info):
+ return segment_info['getcwd']()
+
+ def __call__(self, pl, segment_info, create_watcher):
+ name = self.get_directory(segment_info)
+ if name:
+ repo = guess(path=name, create_watcher=create_watcher)
+ if repo is not None:
+ stash = getattr(repo, 'stash', None)
+ if stash:
+ stashes = stash()
+ if stashes:
+ return [{
+ 'contents': str(stashes),
+ 'highlight_groups': ['stash'],
+ 'divider_highlight_group': self.divider_highlight_group
+ }]
+
+stash = with_docstring(StashSegment(),
+'''Return the number of current VCS stash entries, if any.
+
+Highlight groups used: ``stash``.
+''')
diff --git a/powerline/segments/common/wthr.py b/powerline/segments/common/wthr.py
new file mode 100644
index 0000000..2c54cca
--- /dev/null
+++ b/powerline/segments/common/wthr.py
@@ -0,0 +1,234 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import json
+from collections import namedtuple
+
+from powerline.lib.url import urllib_read, urllib_urlencode
+from powerline.lib.threaded import KwThreadedSegment
+from powerline.segments import with_docstring
+
+
+_WeatherKey = namedtuple('Key', 'location_query weather_api_key')
+
+
+# XXX Warning: module name must not be equal to the segment name as long as this
+# segment is imported into powerline.segments.common module.
+
+
+# Weather condition code descriptions available at
+# https://openweathermap.org/weather-conditions
+weather_conditions_codes = {
+ 200: ('stormy',),
+ 201: ('stormy',),
+ 202: ('stormy',),
+ 210: ('stormy',),
+ 211: ('stormy',),
+ 212: ('stormy',),
+ 221: ('stormy',),
+ 230: ('stormy',),
+ 231: ('stormy',),
+ 232: ('stormy',),
+ 300: ('rainy',),
+ 301: ('rainy',),
+ 302: ('rainy',),
+ 310: ('rainy',),
+ 311: ('rainy',),
+ 312: ('rainy',),
+ 313: ('rainy',),
+ 314: ('rainy',),
+ 321: ('rainy',),
+ 500: ('rainy',),
+ 501: ('rainy',),
+ 502: ('rainy',),
+ 503: ('rainy',),
+ 504: ('rainy',),
+ 511: ('snowy',),
+ 520: ('rainy',),
+ 521: ('rainy',),
+ 522: ('rainy',),
+ 531: ('rainy',),
+ 600: ('snowy',),
+ 601: ('snowy',),
+ 602: ('snowy',),
+ 611: ('snowy',),
+ 612: ('snowy',),
+ 613: ('snowy',),
+ 615: ('snowy',),
+ 616: ('snowy',),
+ 620: ('snowy',),
+ 621: ('snowy',),
+ 622: ('snowy',),
+ 701: ('foggy',),
+ 711: ('foggy',),
+ 721: ('foggy',),
+ 731: ('foggy',),
+ 741: ('foggy',),
+ 751: ('foggy',),
+ 761: ('foggy',),
+ 762: ('foggy',),
+ 771: ('foggy',),
+ 781: ('foggy',),
+ 800: ('sunny',),
+ 801: ('cloudy',),
+ 802: ('cloudy',),
+ 803: ('cloudy',),
+ 804: ('cloudy',),
+}
+
+weather_conditions_icons = {
+ 'day': 'DAY',
+ 'blustery': 'WIND',
+ 'rainy': 'RAIN',
+ 'cloudy': 'CLOUDS',
+ 'snowy': 'SNOW',
+ 'stormy': 'STORM',
+ 'foggy': 'FOG',
+ 'sunny': 'SUN',
+ 'night': 'NIGHT',
+ 'windy': 'WINDY',
+ 'not_available': 'NA',
+ 'unknown': 'UKN',
+}
+
+temp_conversions = {
+ 'C': lambda temp: temp - 273.15,
+ 'F': lambda temp: (temp * 9 / 5) - 459.67,
+ 'K': lambda temp: temp,
+}
+
+# Note: there are also unicode characters for units: ℃, ℉ and K
+temp_units = {
+ 'C': '°C',
+ 'F': '°F',
+ 'K': 'K',
+}
+
+
+class WeatherSegment(KwThreadedSegment):
+ interval = 600
+ default_location = None
+ location_urls = {}
+ weather_api_key = "fbc9549d91a5e4b26c15be0dbdac3460"
+
+ @staticmethod
+ def key(location_query=None, **kwargs):
+ try:
+ weather_api_key = kwargs["weather_api_key"]
+ except KeyError:
+ weather_api_key = WeatherSegment.weather_api_key
+ return _WeatherKey(location_query, weather_api_key)
+
+ def get_request_url(self, weather_key):
+ try:
+ return self.location_urls[weather_key]
+ except KeyError:
+ query_data = {
+ "appid": weather_key.weather_api_key
+ }
+ location_query = weather_key.location_query
+ if location_query is None:
+ location_data = json.loads(urllib_read('https://freegeoip.app/json/'))
+ query_data["lat"] = location_data["latitude"]
+ query_data["lon"] = location_data["longitude"]
+ else:
+ query_data["q"] = location_query
+ self.location_urls[location_query] = url = (
+ "https://api.openweathermap.org/data/2.5/weather?" +
+ urllib_urlencode(query_data))
+ return url
+
+ def compute_state(self, weather_key):
+ url = self.get_request_url(weather_key)
+ raw_response = urllib_read(url)
+ if not raw_response:
+ self.error('Failed to get response')
+ return None
+
+ response = json.loads(raw_response)
+ try:
+ condition = response['weather'][0]
+ condition_code = int(condition['id'])
+ temp = float(response['main']['temp'])
+ except (KeyError, ValueError):
+ self.exception('OpenWeatherMap returned malformed or unexpected response: {0}', repr(raw_response))
+ return None
+
+ try:
+ icon_names = weather_conditions_codes[condition_code]
+ except IndexError:
+ icon_names = ('unknown',)
+ self.error('Unknown condition code: {0}', condition_code)
+
+ return (temp, icon_names)
+
+ def render_one(self, weather, icons=None, unit='C', temp_format=None, temp_coldest=-30, temp_hottest=40, **kwargs):
+ if not weather:
+ return None
+
+ temp, icon_names = weather
+
+ for icon_name in icon_names:
+ if icons:
+ if icon_name in icons:
+ icon = icons[icon_name]
+ break
+ else:
+ icon = weather_conditions_icons[icon_names[-1]]
+
+ temp_format = temp_format or ('{temp:.0f}' + temp_units[unit])
+ converted_temp = temp_conversions[unit](temp)
+ if converted_temp <= temp_coldest:
+ gradient_level = 0
+ elif converted_temp >= temp_hottest:
+ gradient_level = 100
+ else:
+ gradient_level = (converted_temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest)
+ groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather']
+ return [
+ {
+ 'contents': icon + ' ',
+ 'highlight_groups': groups,
+ 'divider_highlight_group': 'background:divider',
+ },
+ {
+ 'contents': temp_format.format(temp=converted_temp),
+ 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'],
+ 'divider_highlight_group': 'background:divider',
+ 'gradient_level': gradient_level,
+ },
+ ]
+
+
+weather = with_docstring(WeatherSegment(),
+'''Return weather from OpenWeatherMaps.
+
+Uses GeoIP lookup from https://freegeoip.app to automatically determine
+your current location. This should be changed if you’re in a VPN or if your
+IP address is registered at another location.
+
+Returns a list of colorized icon and temperature segments depending on
+weather conditions.
+
+:param str unit:
+ temperature unit, can be one of ``F``, ``C`` or ``K``
+:param str location_query:
+ location query for your current location, e.g. ``oslo, norway``
+:param dict icons:
+ dict for overriding default icons, e.g. ``{'heavy_snow' : u'❆'}``
+:param str temp_format:
+ format string, receives ``temp`` as an argument. Should also hold unit.
+:param float temp_coldest:
+ coldest temperature. Any temperature below it will have gradient level equal
+ to zero.
+:param float temp_hottest:
+ hottest temperature. Any temperature above it will have gradient level equal
+ to 100. Temperatures between ``temp_coldest`` and ``temp_hottest`` receive
+ gradient level that indicates relative position in this interval
+ (``100 * (cur-coldest) / (hottest-coldest)``).
+
+Divider highlight group used: ``background:divider``.
+
+Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_gradient`` (gradient) or ``weather``.
+Also uses ``weather_conditions_{condition}`` for all weather conditions supported by OpenWeatherMap.
+''')
diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py
new file mode 100644
index 0000000..57ab377
--- /dev/null
+++ b/powerline/segments/i3wm.py
@@ -0,0 +1,309 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import re
+
+from powerline.theme import requires_segment_info
+from powerline.bindings.wm import get_i3_connection
+
+WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?')
+
+def workspace_groups(w):
+ group = []
+ if w.focused:
+ group.append('workspace:focused')
+ group.append('w_focused')
+ if w.urgent:
+ group.append('workspace:urgent')
+ group.append('w_urgent')
+ if w.visible:
+ group.append('workspace:visible')
+ group.append('w_visible')
+ group.append('workspace')
+ return group
+
+
+def format_name(name, strip=False):
+ if strip:
+ return WORKSPACE_REGEX.sub('', name, count=1)
+ return name
+
+
+def is_empty_workspace(workspace, containers):
+ if workspace.focused or workspace.visible:
+ return False
+ wins = [win for win in containers[workspace.name].leaves()]
+ return False if len(wins) > 0 else True
+
+WS_ICONS = {"multiple": "M"}
+
+def get_icon(workspace, separator, icons, show_multiple_icons, ws_containers):
+ icons_tmp = WS_ICONS
+ icons_tmp.update(icons)
+ icons = icons_tmp
+
+ wins = [win for win in ws_containers[workspace.name].leaves() \
+ if win.parent.scratchpad_state == 'none']
+ if len(wins) == 0:
+ return ''
+
+ result = ''
+ cnt = 0
+ for key in icons:
+ if not icons[key] or len(icons[key]) < 1:
+ continue
+ if any(key in win.window_class for win in wins if win.window_class):
+ result += (separator if cnt > 0 else '') + icons[key]
+ cnt += 1
+ if not show_multiple_icons and cnt > 1:
+ if 'multiple' in icons:
+ return icons['multiple']
+ else:
+ return ''
+ return result
+
+@requires_segment_info
+def workspaces(pl, segment_info, only_show=None, output=None, strip=0, format='{name}',
+ icons=WS_ICONS, sort_workspaces=False, show_output=False, priority_workspaces=[],
+ hide_empty_workspaces=False):
+ '''Return list of used workspaces
+
+ :param list only_show:
+ Specifies which workspaces to show. Valid entries are ``"visible"``,
+ ``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces
+ are shown.
+ :param str output:
+ May be set to the name of an X output. If specified, only workspaces
+ on that output are shown. Overrides automatic output detection by
+ the lemonbar renderer and bindings.
+ Use "__all__" to show workspaces on all outputs.
+ :param int strip:
+ Specifies how many characters from the front of each workspace name
+ should be stripped (e.g. to remove workspace numbers). Defaults to zero.
+ :param str format:
+ Specifies the format used to display workspaces; defaults to ``{name}``.
+ Valid fields are: ``name`` (workspace name), ``number`` (workspace number
+ if present), `stipped_name`` (workspace name stripped of leading number),
+ ``icon`` (if available, icon for application running in the workspace,
+ uses the ``multiple`` icon instead of multiple different icons), ``multi_icon``
+ (similar to ``icon``, but does not use ``multiple``, instead joins all icons
+ with a single space)
+ :param dict icons:
+ A dictionary mapping a substring of window classes to strings to be used as an
+ icon for that window class.
+ Further, there is a ``multiple`` icon for workspaces containing more than one
+ window.
+ :param bool sort_workspaces:
+ Sort the workspaces displayed by their name according to the natural ordering.
+ :param bool show_output:
+ Shows the name of the output if more than one output is connected.
+ :param list priority_workspaces:
+ A list of workspace names to be sorted before any other workspaces in the given
+ order.
+ :param bool hide_empty_workspaces:
+ Hides all workspaces without any open window.
+ Also hides non-focussed workspaces containing only an open scratchpad.
+
+
+ Highlight groups used: ``workspace`` or ``w_visible``, ``workspace:visible``, ``workspace`` or ``w_focused``, ``workspace:focused``, ``workspace`` or ``w_urgent``, ``workspace:urgent``, ``workspace`` or ``output``.
+ '''
+ conn = get_i3_connection()
+
+ if not output == "__all__":
+ output = output or segment_info.get('output')
+ else:
+ output = None
+
+ if output:
+ output = [output]
+ else:
+ output = [o.name for o in conn.get_outputs() if o.active]
+
+
+ def sort_ws(ws):
+ if sort_workspaces:
+ def natural_key(ws):
+ str = ws.name
+ return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', str)]
+ ws = sorted(ws, key=natural_key)
+ result = []
+ for n in priority_workspaces:
+ result += [w for w in ws if w.name == n]
+ return result + [w for w in ws if not w.name in priority_workspaces]
+
+ ws_containers = {w_con.name : w_con for w_con in conn.get_tree().workspaces()}
+
+ if len(output) <= 1:
+ res = []
+ if show_output:
+ res += [{
+ 'contents': output[0],
+ 'highlight_groups': ['output']
+ }]
+ res += [{
+ 'contents': format.format(name = w.name[min(len(w.name), strip):],
+ stripped_name = format_name(w.name, strip=True),
+ number = w.num,
+ icon = get_icon(w, '', icons, False, ws_containers),
+ multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
+ 'highlight_groups': workspace_groups(w)
+ } for w in sort_ws(conn.get_workspaces()) \
+ if (not only_show or any(getattr(w, tp) for tp in only_show)) \
+ if w.output == output[0] \
+ if not (hide_empty_workspaces and is_empty_workspace(w, ws_containers))]
+ return res
+ else:
+ res = []
+ for n in output:
+ if show_output:
+ res += [{
+ 'contents': n,
+ 'highlight_groups': ['output']
+ }]
+ res += [{
+ 'contents': format.format(name = w.name[min(len(w.name), strip):],
+ stripped_name = format_name(w.name, strip=True),
+ number = w.num,
+ icon = get_icon(w, '', icons, False, ws_containers),
+ multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
+ 'highlight_groups': workspace_groups(w)
+ } for w in sort_ws(conn.get_workspaces()) \
+ if (not only_show or any(getattr(w, tp) for tp in only_show)) \
+ if w.output == n \
+ if not (hide_empty_workspaces and is_empty_workspace(w, ws_containers))]
+ return res
+
+@requires_segment_info
+def workspace(pl, segment_info, workspace=None, strip=False, format=None, icons=WS_ICONS):
+ '''Return the specified workspace name
+
+ :param str workspace:
+ Specifies which workspace to show. If unspecified, may be set by the
+ ``list_workspaces`` lister if used, otherwise falls back to
+ currently focused workspace.
+
+ :param bool strip:
+ Specifies whether workspace numbers (in the ``1: name`` format) should
+ be stripped from workspace names before being displayed. Defaults to false.
+ Deprecated: Use {name} or {stripped_name} of format instead.
+
+ :param str format:
+ Specifies the format used to display workspaces; defaults to ``{name}``.
+ Valid fields are: ``name`` (workspace name), ``number`` (workspace number
+ if present), `stipped_name`` (workspace name stripped of leading number),
+ ``icon`` (if available, icon for application running in the workspace,
+ uses the ``multiple`` icon instead of multiple different icons), ``multi_icon``
+ (similar to ``icon``, but does not use ``multiple``, instead joins all icons
+ with a single space)
+
+ :param dict icons:
+ A dictionary mapping a substring of window classes to strings to be used as an
+ icon for that window class.
+ Further, there is a ``multiple`` icon for workspaces containing more than one
+ window.
+
+ Highlight groups used: ``workspace`` or ``w_visible``, ``workspace:visible``, ``workspace`` or ``w_focused``, ``workspace:focused``, ``workspace`` or ``w_urgent``, ``workspace:urgent``, ``workspace``.
+ '''
+ if format == None:
+ format = '{stripped_name}' if strip else '{name}'
+
+ conn = get_i3_connection()
+ ws_containers = {w_con.name : w_con for w_con in conn.get_tree().workspaces()}
+
+ if workspace:
+ try:
+ w = next((
+ w for w in conn.get_workspaces()
+ if w.name == workspace
+ ))
+ except StopIteration:
+ return None
+ elif segment_info.get('workspace'):
+ w = segment_info['workspace']
+ else:
+ try:
+ w = next((
+ w for w in conn.get_workspaces()
+ if w.focused
+ ))
+ except StopIteration:
+ return None
+
+ return [{
+ 'contents': format.format(name = w.name,
+ stripped_name = format_name(w.name, strip=True),
+ number = w.num,
+ icon = get_icon(w, '', icons, False, ws_containers),
+ multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
+ 'highlight_groups': workspace_groups(w)
+ }]
+
+
+@requires_segment_info
+def mode(pl, segment_info, names={'default': None}):
+ '''Returns current i3 mode
+
+ :param dict names:
+ Specifies the string to show for various modes.
+ Use ``null`` to hide a mode (``default`` is hidden by default).
+
+ Highligh groups used: ``mode``
+ '''
+ mode = segment_info['mode']
+ if mode in names:
+ return names[mode]
+ return mode
+
+
+def scratchpad_groups(w):
+ group = []
+ if w.urgent:
+ group.append('scratchpad:urgent')
+ if w.nodes[0].focused:
+ group.append('scratchpad:focused')
+ if w.workspace().name != '__i3_scratch':
+ group.append('scratchpad:visible')
+ group.append('scratchpad')
+ return group
+
+
+SCRATCHPAD_ICONS = {
+ 'fresh': 'O',
+ 'changed': 'X',
+}
+
+
+def scratchpad(pl, icons=SCRATCHPAD_ICONS):
+ '''Returns the windows currently on the scratchpad
+
+ :param dict icons:
+ Specifies the strings to show for the different scratchpad window states. Must
+ contain the keys ``fresh`` and ``changed``.
+
+ Highlight groups used: ``scratchpad`` or ``scratchpad:visible``, ``scratchpad`` or ``scratchpad:focused``, ``scratchpad`` or ``scratchpad:urgent``.
+ '''
+
+ return [
+ {
+ 'contents': icons.get(w.scratchpad_state, icons['changed']),
+ 'highlight_groups': scratchpad_groups(w)
+ }
+ for w in get_i3_connection().get_tree().descendants()
+ if w.scratchpad_state != 'none'
+ ]
+
+def active_window(pl, cutoff=100):
+ '''Returns the title of the currently active window.
+
+ :param int cutoff:
+ Maximum title length. If the title is longer, the window_class is used instead.
+
+ Highlight groups used: ``active_window_title``.
+ '''
+
+ focused = get_i3_connection().get_tree().find_focused()
+ cont = focused.name
+ if len(cont) > cutoff:
+ cont = focused.window_class
+
+ return [{'contents': cont, 'highlight_groups': ['active_window_title']}] if focused.name != focused.workspace().name else []
diff --git a/powerline/segments/ipython.py b/powerline/segments/ipython.py
new file mode 100644
index 0000000..622e0a5
--- /dev/null
+++ b/powerline/segments/ipython.py
@@ -0,0 +1,9 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.theme import requires_segment_info
+
+
+@requires_segment_info
+def prompt_count(pl, segment_info):
+ return str(segment_info['ipython'].prompt_count)
diff --git a/powerline/segments/pdb.py b/powerline/segments/pdb.py
new file mode 100644
index 0000000..bd6a38b
--- /dev/null
+++ b/powerline/segments/pdb.py
@@ -0,0 +1,61 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+
+from powerline.theme import requires_segment_info
+
+
+@requires_segment_info
+def current_line(pl, segment_info):
+ '''Displays line number that is next to be run
+ '''
+ return str(segment_info['curframe'].f_lineno)
+
+
+@requires_segment_info
+def current_file(pl, segment_info, basename=True):
+ '''Displays current file name
+
+ :param bool basename:
+ If true only basename is displayed.
+ '''
+ filename = segment_info['curframe'].f_code.co_filename
+ if basename:
+ filename = os.path.basename(filename)
+ return filename
+
+
+@requires_segment_info
+def current_code_name(pl, segment_info):
+ '''Displays name of the code object of the current frame
+ '''
+ return segment_info['curframe'].f_code.co_name
+
+
+@requires_segment_info
+def current_context(pl, segment_info):
+ '''Displays currently executed context name
+
+ This is similar to :py:func:`current_code_name`, but gives more details.
+
+ Currently it only gives module file name if code_name happens to be
+ ``<module>``.
+ '''
+ name = segment_info['curframe'].f_code.co_name
+ if name == '<module>':
+ name = os.path.basename(segment_info['curframe'].f_code.co_filename)
+ return name
+
+
+@requires_segment_info
+def stack_depth(pl, segment_info, full_stack=False):
+ '''Displays current stack depth
+
+ Result is relative to the stack depth at the time prompt was first run.
+
+ :param bool full_stack:
+ If true then absolute depth is used.
+ '''
+ return str(len(segment_info['pdb'].stack) - (
+ 0 if full_stack else segment_info['initial_stack_length']))
diff --git a/powerline/segments/shell.py b/powerline/segments/shell.py
new file mode 100644
index 0000000..66991c7
--- /dev/null
+++ b/powerline/segments/shell.py
@@ -0,0 +1,196 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.theme import requires_segment_info
+from powerline.segments import with_docstring
+from powerline.segments.common.env import CwdSegment
+from powerline.lib.unicode import out_u
+
+
+@requires_segment_info
+def jobnum(pl, segment_info, show_zero=False):
+ '''Return the number of jobs.
+
+ :param bool show_zero:
+ If False (default) shows nothing if there are no jobs. Otherwise shows
+ zero for no jobs.
+ '''
+ jobnum = segment_info['args'].jobnum
+ if jobnum is None or (not show_zero and jobnum == 0):
+ return None
+ else:
+ return str(jobnum)
+
+try:
+ import signal
+ exit_codes = dict((k, v) for v, k in reversed(sorted(signal.__dict__.items())) \
+ if v.startswith('SIG') and not v.startswith('SIG_'))
+except ImportError:
+ exit_codes = dict()
+
+@requires_segment_info
+def last_status(pl, segment_info, signal_names=True):
+ '''Return last exit code.
+
+ :param bool signal_names:
+ If True (default), translate signal numbers to human-readable names.
+
+ Highlight groups used: ``exit_fail``
+ '''
+ if not segment_info['args'].last_exit_code:
+ return None
+
+ try:
+ if signal_names and segment_info['args'].last_exit_code - 128 in exit_codes:
+ return [{'contents': exit_codes[segment_info['args'].last_exit_code - 128], 'highlight_groups': ['exit_fail']}]
+ except TypeError:
+ pass
+ return [{'contents': str(segment_info['args'].last_exit_code), 'highlight_groups': ['exit_fail']}]
+
+
+@requires_segment_info
+def last_pipe_status(pl, segment_info, signal_names=True):
+ '''Return last pipe status.
+
+ :param bool signal_names:
+ If True (default), translate signal numbers to human-readable names.
+
+ Highlight groups used: ``exit_fail``, ``exit_success``
+ '''
+ last_pipe_status = (
+ segment_info['args'].last_pipe_status
+ or (segment_info['args'].last_exit_code,)
+ )
+ if any(last_pipe_status):
+ try:
+ return [{
+ 'contents': exit_codes[status - 128] if signal_names and \
+ status - 128 in exit_codes else str(status),
+ 'highlight_groups': ['exit_fail' if status else 'exit_success'],
+ 'draw_inner_divider': True
+ } for status in last_pipe_status]
+ except TypeError:
+ return [{
+ 'contents': str(status),
+ 'highlight_groups': ['exit_fail' if status else 'exit_success'],
+ 'draw_inner_divider': True
+ } for status in last_pipe_status]
+ else:
+ return None
+
+@requires_segment_info
+def mode(pl, segment_info, override={'vicmd': 'COMMND', 'viins': 'INSERT'}, default=None):
+ '''Return the current mode.
+
+ :param dict override:
+ dict for overriding mode strings.
+ :param str default:
+ If current mode is equal to this string then this segment will not get
+ displayed. If not specified the value is taken from
+ ``$POWERLINE_DEFAULT_MODE`` variable. This variable is set by zsh
+ bindings for any mode that does not start from ``vi``.
+ '''
+ mode = segment_info.get('mode', None)
+ if not mode:
+ pl.debug('No mode specified')
+ return None
+ default = default or segment_info.get('default_mode', None)
+ if mode == default:
+ return None
+ try:
+ return override[mode]
+ except KeyError:
+ # Note: with zsh line editor you can emulate as much modes as you wish.
+ # Thus having unknown mode is not an error: maybe just some developer
+ # added support for his own zle widgets. As there is no built-in mode()
+ # function like in VimL and mode is likely be defined by our code or by
+ # somebody knowing what he is doing there is absolutely no need in
+ # keeping translations dictionary.
+ return mode.upper()
+
+
+@requires_segment_info
+def continuation(pl, segment_info, omit_cmdsubst=True, right_align=False, renames={}):
+ '''Display parser state.
+
+ :param bool omit_cmdsubst:
+ Do not display cmdsubst parser state if it is the last one.
+ :param bool right_align:
+ Align to the right.
+ :param dict renames:
+ Rename states: ``{old_name : new_name}``. If ``new_name`` is ``None``
+ then given state is not displayed.
+
+ Highlight groups used: ``continuation``, ``continuation:current``.
+ '''
+ if not segment_info.get('parser_state'):
+ return [{
+ 'contents': '',
+ 'width': 'auto',
+ 'highlight_groups': ['continuation:current', 'continuation'],
+ }]
+ ret = []
+
+ for state in segment_info['parser_state'].split():
+ state = renames.get(state, state)
+ if state:
+ ret.append({
+ 'contents': state,
+ 'highlight_groups': ['continuation'],
+ 'draw_inner_divider': True,
+ })
+
+ if omit_cmdsubst and ret[-1]['contents'] == 'cmdsubst':
+ ret.pop(-1)
+
+ if not ret:
+ ret.append({
+ 'contents': ''
+ })
+
+ if right_align:
+ ret[0].update(width='auto', align='r')
+ ret[-1]['highlight_groups'] = ['continuation:current', 'continuation']
+ else:
+ ret[-1].update(width='auto', align='l', highlight_groups=['continuation:current', 'continuation'])
+
+ return ret
+
+
+@requires_segment_info
+class ShellCwdSegment(CwdSegment):
+ def get_shortened_path(self, pl, segment_info, use_shortened_path=True, **kwargs):
+ if use_shortened_path:
+ try:
+ return out_u(segment_info['shortened_path'])
+ except KeyError:
+ pass
+ return super(ShellCwdSegment, self).get_shortened_path(pl, segment_info, **kwargs)
+
+
+cwd = with_docstring(ShellCwdSegment(),
+'''Return the current working directory.
+
+Returns a segment list to create a breadcrumb-like effect.
+
+:param int dir_shorten_len:
+ shorten parent directory names to this length (e.g.
+ :file:`/long/path/to/powerline` → :file:`/l/p/t/powerline`)
+:param int dir_limit_depth:
+ limit directory depth to this number (e.g.
+ :file:`/long/path/to/powerline` → :file:`⋯/to/powerline`)
+:param bool use_path_separator:
+ Use path separator in place of soft divider.
+:param bool use_shortened_path:
+ Use path from shortened_path ``--renderer-arg`` argument. If this argument
+ is present ``shorten_home`` argument is ignored.
+:param bool shorten_home:
+ Shorten home directory to ``~``.
+:param str ellipsis:
+ Specifies what to use in place of omitted directories. Use None to not
+ show this subsegment at all.
+
+Divider highlight group used: ``cwd:divider``.
+
+Highlight groups used: ``cwd:current_folder`` or ``cwd``. It is recommended to define all highlight groups.
+''')
diff --git a/powerline/segments/tmux.py b/powerline/segments/tmux.py
new file mode 100644
index 0000000..1f34389
--- /dev/null
+++ b/powerline/segments/tmux.py
@@ -0,0 +1,22 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.bindings.tmux import get_tmux_output
+
+
+def attached_clients(pl, minimum=1):
+ '''Return the number of tmux clients attached to the currently active session
+
+ :param int minimum:
+ The minimum number of attached clients that must be present for this
+ segment to be visible.
+ '''
+ session_output = get_tmux_output(pl, 'list-panes', '-F', '#{session_name}')
+ if not session_output:
+ return None
+ session_name = session_output.rstrip().split('\n')[0]
+
+ attached_clients_output = get_tmux_output(pl, 'list-clients', '-t', session_name)
+ attached_count = len(attached_clients_output.rstrip().split('\n'))
+
+ return None if attached_count < minimum else str(attached_count)
diff --git a/powerline/segments/vim/__init__.py b/powerline/segments/vim/__init__.py
new file mode 100644
index 0000000..d999d07
--- /dev/null
+++ b/powerline/segments/vim/__init__.py
@@ -0,0 +1,805 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import os
+import re
+import csv
+import sys
+
+from collections import defaultdict
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption,
+ buffer_name, vim_getwinvar,
+ register_buffer_cache, current_tabpage,
+ list_tabpage_buffers_segment_info)
+from powerline.theme import requires_segment_info, requires_filesystem_watcher
+from powerline.lib import add_divider_highlight_group
+from powerline.lib.vcs import guess
+from powerline.lib.humanize_bytes import humanize_bytes
+from powerline.lib import wraps_saveargs as wraps
+from powerline.segments.common.vcs import BranchSegment, StashSegment
+from powerline.segments import with_docstring
+from powerline.lib.unicode import string, unicode
+
+try:
+ from __builtin__ import xrange as range
+except ImportError:
+ pass
+
+
+vim_funcs = {
+ 'virtcol': vim_get_func('virtcol', rettype='int'),
+ 'getpos': vim_get_func('getpos'),
+ 'fnamemodify': vim_get_func('fnamemodify', rettype='bytes'),
+ 'line2byte': vim_get_func('line2byte', rettype='int'),
+ 'line': vim_get_func('line', rettype='int'),
+}
+
+vim_modes = {
+ 'n': 'NORMAL',
+ 'no': 'N-OPER',
+ 'v': 'VISUAL',
+ 'V': 'V-LINE',
+ '^V': 'V-BLCK',
+ 's': 'SELECT',
+ 'S': 'S-LINE',
+ '^S': 'S-BLCK',
+ 'i': 'INSERT',
+ 'ic': 'I-COMP',
+ 'ix': 'I-C_X ',
+ 'R': 'RPLACE',
+ 'Rv': 'V-RPLC',
+ 'Rc': 'R-COMP',
+ 'Rx': 'R-C_X ',
+ 'c': 'COMMND',
+ 'cv': 'VIM-EX',
+ 'ce': 'NRM-EX',
+ 'r': 'PROMPT',
+ 'rm': '-MORE-',
+ 'r?': 'CNFIRM',
+ '!': '!SHELL',
+ 't': 'TERM ',
+}
+
+
+# TODO Remove cache when needed
+def window_cached(func):
+ cache = {}
+
+ @requires_segment_info
+ @wraps(func)
+ def ret(segment_info, **kwargs):
+ window_id = segment_info['window_id']
+ if segment_info['mode'] == 'nc':
+ return cache.get(window_id)
+ else:
+ if getattr(func, 'powerline_requires_segment_info', False):
+ r = func(segment_info=segment_info, **kwargs)
+ else:
+ r = func(**kwargs)
+ cache[window_id] = r
+ return r
+
+ return ret
+
+
+@requires_segment_info
+def mode(pl, segment_info, override=None):
+ '''Return the current vim mode.
+
+ If mode (returned by ``mode()`` VimL function, see ``:h mode()`` in Vim)
+ consists of multiple characters and necessary mode is not known to powerline
+ then it will fall back to mode with last character(s) ignored.
+
+ :param dict override:
+ dict for overriding default mode strings, e.g. ``{ 'n': 'NORM' }``
+ '''
+ mode = segment_info['mode']
+ if mode == 'nc':
+ return None
+ while mode:
+ try:
+ if not override:
+ return vim_modes[mode]
+ try:
+ return override[mode]
+ except KeyError:
+ return vim_modes[mode]
+ except KeyError:
+ mode = mode[:-1]
+ return 'BUG'
+
+
+@window_cached
+@requires_segment_info
+def visual_range(pl, segment_info, CTRL_V_text='{rows} x {vcols}', v_text_oneline='C:{vcols}', v_text_multiline='L:{rows}', V_text='L:{rows}'):
+ '''Return the current visual selection range.
+
+ :param str CTRL_V_text:
+ Text to display when in block visual or select mode.
+ :param str v_text_oneline:
+ Text to display when in charaterwise visual or select mode, assuming
+ selection occupies only one line.
+ :param str v_text_multiline:
+ Text to display when in charaterwise visual or select mode, assuming
+ selection occupies more then one line.
+ :param str V_text:
+ Text to display when in linewise visual or select mode.
+
+ All texts are format strings which are passed the following parameters:
+
+ ========= =============================================================
+ Parameter Description
+ ========= =============================================================
+ sline Line number of the first line of the selection
+ eline Line number of the last line of the selection
+ scol Column number of the first character of the selection
+ ecol Column number of the last character of the selection
+ svcol Virtual column number of the first character of the selection
+ secol Virtual column number of the last character of the selection
+ rows Number of lines in the selection
+ cols Number of columns in the selection
+ vcols Number of virtual columns in the selection
+ ========= =============================================================
+ '''
+ sline, scol, soff = [int(v) for v in vim_funcs['getpos']('v')[1:]]
+ eline, ecol, eoff = [int(v) for v in vim_funcs['getpos']('.')[1:]]
+ svcol = vim_funcs['virtcol']([sline, scol, soff])
+ evcol = vim_funcs['virtcol']([eline, ecol, eoff])
+ rows = abs(eline - sline) + 1
+ cols = abs(ecol - scol) + 1
+ vcols = abs(evcol - svcol) + 1
+ return {
+ '^': CTRL_V_text,
+ 's': v_text_oneline if rows == 1 else v_text_multiline,
+ 'S': V_text,
+ 'v': v_text_oneline if rows == 1 else v_text_multiline,
+ 'V': V_text,
+ }.get(segment_info['mode'][0], '').format(
+ sline=sline, eline=eline,
+ scol=scol, ecol=ecol,
+ svcol=svcol, evcol=evcol,
+ rows=rows, cols=cols, vcols=vcols,
+ )
+
+
+@requires_segment_info
+def modified_indicator(pl, segment_info, text='+'):
+ '''Return a file modified indicator.
+
+ :param string text:
+ text to display if the current buffer is modified
+ '''
+ return text if int(vim_getbufoption(segment_info, 'modified')) else None
+
+
+@requires_segment_info
+def tab_modified_indicator(pl, segment_info, text='+'):
+ '''Return a file modified indicator for tabpages.
+
+ :param string text:
+ text to display if any buffer in the current tab is modified
+
+ Highlight groups used: ``tab_modified_indicator`` or ``modified_indicator``.
+ '''
+ for buf_segment_info in list_tabpage_buffers_segment_info(segment_info):
+ if int(vim_getbufoption(buf_segment_info, 'modified')):
+ return [{
+ 'contents': text,
+ 'highlight_groups': ['tab_modified_indicator', 'modified_indicator'],
+ }]
+ return None
+
+
+@requires_segment_info
+def paste_indicator(pl, segment_info, text='PASTE'):
+ '''Return a paste mode indicator.
+
+ :param string text:
+ text to display if paste mode is enabled
+ '''
+ return text if int(vim.eval('&paste')) else None
+
+
+@requires_segment_info
+def readonly_indicator(pl, segment_info, text='RO'):
+ '''Return a read-only indicator.
+
+ :param string text:
+ text to display if the current buffer is read-only
+ '''
+ return text if int(vim_getbufoption(segment_info, 'readonly')) else None
+
+
+SCHEME_RE = re.compile(b'^\\w[\\w\\d+\\-.]*(?=:)')
+
+
+@requires_segment_info
+def file_scheme(pl, segment_info):
+ '''Return the protocol part of the file.
+
+ Protocol is the part of the full filename just before the colon which
+ starts with a latin letter and contains only latin letters, digits, plus,
+ period or hyphen (refer to `RFC3986
+ <http://tools.ietf.org/html/rfc3986#section-3.1>`_ for the description of
+ URI scheme). If there is no such a thing ``None`` is returned, effectively
+ removing segment.
+
+ .. note::
+ Segment will not check whether there is ``//`` just after the
+ colon or if there is at least one slash after the scheme. Reason: it is
+ not always present. E.g. when opening file inside a zip archive file
+ name will look like :file:`zipfile:/path/to/archive.zip::file.txt`.
+ ``file_scheme`` segment will catch ``zipfile`` part here.
+ '''
+ name = buffer_name(segment_info)
+ if not name:
+ return None
+ match = SCHEME_RE.match(name)
+ if match:
+ return match.group(0).decode('ascii')
+
+
+@requires_segment_info
+def file_directory(pl, segment_info, remove_scheme=True, shorten_user=True, shorten_cwd=True, shorten_home=False):
+ '''Return file directory (head component of the file path).
+
+ :param bool remove_scheme:
+ Remove scheme part from the segment name, if present. See documentation
+ of file_scheme segment for the description of what scheme is. Also
+ removes the colon.
+
+ :param bool shorten_user:
+ Shorten ``$HOME`` directory to :file:`~/`. Does not work for files with
+ scheme.
+
+ :param bool shorten_cwd:
+ Shorten current directory to :file:`./`. Does not work for files with
+ scheme present.
+
+ :param bool shorten_home:
+ Shorten all directories in :file:`/home/` to :file:`~user/` instead of
+ :file:`/home/user/`. Does not work for files with scheme present.
+ '''
+ name = buffer_name(segment_info)
+ if not name:
+ return None
+ match = SCHEME_RE.match(name)
+ if match:
+ if remove_scheme:
+ name = name[len(match.group(0)) + 1:] # Remove scheme and colon
+ file_directory = vim_funcs['fnamemodify'](name, ':h')
+ else:
+ file_directory = vim_funcs['fnamemodify'](
+ name,
+ (':~' if shorten_user else '') + (':.' if shorten_cwd else '') + ':h'
+ )
+ if not file_directory:
+ return None
+ if shorten_home and file_directory.startswith('/home/'):
+ file_directory = b'~' + file_directory[6:]
+ file_directory = file_directory.decode(segment_info['encoding'], 'powerline_vim_strtrans_error')
+ return file_directory + os.sep
+
+
+@requires_segment_info
+def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]'):
+ '''Return file name (tail component of the file path).
+
+ :param bool display_no_file:
+ display a string if the buffer is missing a file name
+ :param str no_file_text:
+ the string to display if the buffer is missing a file name
+
+ Highlight groups used: ``file_name_no_file`` or ``file_name``, ``file_name``.
+ '''
+ name = buffer_name(segment_info)
+ if not name:
+ if display_no_file:
+ return [{
+ 'contents': no_file_text,
+ 'highlight_groups': ['file_name_no_file', 'file_name'],
+ }]
+ else:
+ return None
+ return os.path.basename(name).decode(segment_info['encoding'], 'powerline_vim_strtrans_error')
+
+
+@window_cached
+def file_size(pl, suffix='B', si_prefix=False):
+ '''Return file size in &encoding.
+
+ :param str suffix:
+ string appended to the file size
+ :param bool si_prefix:
+ use SI prefix, e.g. MB instead of MiB
+ :return: file size or None if the file isn’t saved or if the size is too big to fit in a number
+ '''
+ # Note: returns file size in &encoding, not in &fileencoding. But returned
+ # size is updated immediately; and it is valid for any buffer
+ file_size = vim_funcs['line2byte'](len(vim.current.buffer) + 1) - 1
+ if file_size < 0:
+ file_size = 0
+ return humanize_bytes(file_size, suffix, si_prefix)
+
+
+@requires_segment_info
+@add_divider_highlight_group('background:divider')
+def file_format(pl, segment_info):
+ '''Return file format (i.e. line ending type).
+
+ :return: file format or None if unknown or missing file format
+
+ Divider highlight group used: ``background:divider``.
+ '''
+ return vim_getbufoption(segment_info, 'fileformat') or None
+
+
+@requires_segment_info
+@add_divider_highlight_group('background:divider')
+def file_encoding(pl, segment_info):
+ '''Return file encoding/character set.
+
+ :return: file encoding/character set or None if unknown or missing file encoding
+
+ Divider highlight group used: ``background:divider``.
+ '''
+ return vim_getbufoption(segment_info, 'fileencoding') or None
+
+
+@requires_segment_info
+@add_divider_highlight_group('background:divider')
+def file_bom(pl, segment_info):
+ '''Return BOM of the current file
+
+ :return: Byte order mark or None if unknown or missing BOM
+
+ Divider highlight group used: ``background:divider``.
+ '''
+ return 'bom' if vim_getbufoption(segment_info, 'bomb') else None
+
+
+@requires_segment_info
+@add_divider_highlight_group('background:divider')
+def file_type(pl, segment_info):
+ '''Return file type.
+
+ :return: file type or None if unknown file type
+
+ Divider highlight group used: ``background:divider``.
+ '''
+ return vim_getbufoption(segment_info, 'filetype') or None
+
+
+@requires_segment_info
+def window_title(pl, segment_info):
+ '''Return the window title.
+
+ This currently looks at the ``quickfix_title`` window variable,
+ which is used by Syntastic and Vim itself.
+
+ It is used in the quickfix theme.'''
+ try:
+ return vim_getwinvar(segment_info, 'quickfix_title')
+ except KeyError:
+ return None
+
+
+@requires_segment_info
+def line_percent(pl, segment_info, gradient=False):
+ '''Return the cursor position in the file as a percentage.
+
+ :param bool gradient:
+ highlight the percentage with a color gradient (by default a green to red gradient)
+
+ Highlight groups used: ``line_percent_gradient`` (gradient), ``line_percent``.
+ '''
+ line_current = segment_info['window'].cursor[0]
+ line_last = len(segment_info['buffer'])
+ percentage = line_current * 100.0 / line_last
+ if not gradient:
+ return str(int(round(percentage)))
+ return [{
+ 'contents': str(int(round(percentage))),
+ 'highlight_groups': ['line_percent_gradient', 'line_percent'],
+ 'gradient_level': percentage,
+ }]
+
+
+@window_cached
+def position(pl, position_strings={'top': 'Top', 'bottom': 'Bot', 'all': 'All'}, gradient=False):
+ '''Return the position of the current view in the file as a percentage.
+
+ :param dict position_strings:
+ dict for translation of the position strings, e.g. ``{"top":"Oben", "bottom":"Unten", "all":"Alles"}``
+
+ :param bool gradient:
+ highlight the percentage with a color gradient (by default a green to red gradient)
+
+ Highlight groups used: ``position_gradient`` (gradient), ``position``.
+ '''
+ line_last = len(vim.current.buffer)
+
+ winline_first = vim_funcs['line']('w0')
+ winline_last = vim_funcs['line']('w$')
+ if winline_first == 1 and winline_last == line_last:
+ percentage = 0.0
+ content = position_strings['all']
+ elif winline_first == 1:
+ percentage = 0.0
+ content = position_strings['top']
+ elif winline_last == line_last:
+ percentage = 100.0
+ content = position_strings['bottom']
+ else:
+ percentage = winline_first * 100.0 / (line_last - winline_last + winline_first)
+ content = str(int(round(percentage))) + '%'
+
+ if not gradient:
+ return content
+ return [{
+ 'contents': content,
+ 'highlight_groups': ['position_gradient', 'position'],
+ 'gradient_level': percentage,
+ }]
+
+
+@requires_segment_info
+def line_current(pl, segment_info):
+ '''Return the current cursor line.'''
+ return str(segment_info['window'].cursor[0])
+
+
+@requires_segment_info
+def line_count(pl, segment_info):
+ '''Return the line count of the current buffer.'''
+ return str(len(segment_info['buffer']))
+
+
+@requires_segment_info
+def col_current(pl, segment_info):
+ '''Return the current cursor column.
+ '''
+ return str(segment_info['window'].cursor[1] + 1)
+
+
+@window_cached
+def virtcol_current(pl, gradient=True):
+ '''Return current visual column with concealed characters ignored
+
+ :param bool gradient:
+ Determines whether it should show textwidth-based gradient (gradient level is ``virtcol * 100 / textwidth``).
+
+ Highlight groups used: ``virtcol_current_gradient`` (gradient), ``virtcol_current`` or ``col_current``.
+ '''
+ col = vim_funcs['virtcol']('.')
+ r = [{'contents': str(col), 'highlight_groups': ['virtcol_current', 'col_current']}]
+ if gradient:
+ textwidth = int(getbufvar('%', '&textwidth'))
+ r[-1]['gradient_level'] = min(col * 100 / textwidth, 100) if textwidth else 0
+ r[-1]['highlight_groups'].insert(0, 'virtcol_current_gradient')
+ return r
+
+
+def modified_buffers(pl, text='+ ', join_str=','):
+ '''Return a comma-separated list of modified buffers.
+
+ :param str text:
+ text to display before the modified buffer list
+ :param str join_str:
+ string to use for joining the modified buffer list
+ '''
+ buffer_mod_text = join_str.join((
+ str(buffer.number)
+ for buffer in vim.buffers
+ if int(vim_getbufoption({'buffer': buffer, 'bufnr': buffer.number}, 'modified'))
+ ))
+ if buffer_mod_text:
+ return text + buffer_mod_text
+ return None
+
+
+@requires_filesystem_watcher
+@requires_segment_info
+class VimBranchSegment(BranchSegment):
+ divider_highlight_group = 'branch:divider'
+
+ @staticmethod
+ def get_directory(segment_info):
+ if vim_getbufoption(segment_info, 'buftype'):
+ return None
+ return buffer_name(segment_info)
+
+
+branch = with_docstring(VimBranchSegment(),
+'''Return the current working branch.
+
+:param bool status_colors:
+ Determines whether repository status will be used to determine highlighting.
+ Default: False.
+:param bool ignore_statuses:
+ List of statuses which will not result in repo being marked as dirty. Most
+ useful is setting this option to ``["U"]``: this will ignore repository
+ which has just untracked files (i.e. repository with modified, deleted or
+ removed files will be marked as dirty, while just untracked files will make
+ segment show clean repository). Only applicable if ``status_colors`` option
+ is True.
+
+Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``.
+
+Divider highlight group used: ``branch:divider``.
+''')
+
+
+@requires_filesystem_watcher
+@requires_segment_info
+class VimStashSegment(StashSegment):
+ divider_highlight_group = 'stash:divider'
+
+ @staticmethod
+ def get_directory(segment_info):
+ if vim_getbufoption(segment_info, 'buftype'):
+ return None
+ return buffer_name(segment_info)
+
+
+stash = with_docstring(VimStashSegment(),
+'''Return the number of stashes in the current working branch.
+
+Highlight groups used: ``stash``.
+''')
+
+
+@requires_filesystem_watcher
+@requires_segment_info
+def file_vcs_status(pl, segment_info, create_watcher):
+ '''Return the VCS status for this buffer.
+
+ Highlight groups used: ``file_vcs_status``.
+ '''
+ name = buffer_name(segment_info)
+ skip = not (name and (not vim_getbufoption(segment_info, 'buftype')))
+ if not skip:
+ repo = guess(path=name, create_watcher=create_watcher)
+ if repo is not None:
+ status = repo.status(os.path.relpath(name, repo.directory))
+ if not status:
+ return None
+ status = status.strip()
+ ret = []
+ for status in status:
+ ret.append({
+ 'contents': status,
+ 'highlight_groups': ['file_vcs_status_' + status, 'file_vcs_status'],
+ })
+ return ret
+
+
+trailing_whitespace_cache = None
+
+
+@requires_segment_info
+def trailing_whitespace(pl, segment_info):
+ '''Return the line number for trailing whitespaces
+
+ It is advised not to use this segment in insert mode: in Insert mode it will
+ iterate over all lines in buffer each time you happen to type a character
+ which may cause lags. It will also show you whitespace warning each time you
+ happen to type space.
+
+ Highlight groups used: ``trailing_whitespace`` or ``warning``.
+ '''
+ global trailing_whitespace_cache
+ if trailing_whitespace_cache is None:
+ trailing_whitespace_cache = register_buffer_cache(defaultdict(lambda: (0, None)))
+ bufnr = segment_info['bufnr']
+ changedtick = getbufvar(bufnr, 'changedtick')
+ if trailing_whitespace_cache[bufnr][0] == changedtick:
+ return trailing_whitespace_cache[bufnr][1]
+ else:
+ buf = segment_info['buffer']
+ bws = b' \t'
+ sws = str(' \t') # Ignore unicode_literals and use native str.
+ for i in range(len(buf)):
+ try:
+ line = buf[i]
+ except UnicodeDecodeError: # May happen in Python 3
+ if hasattr(vim, 'bindeval'):
+ line = vim.bindeval('getbufline({0}, {1})'.format(
+ bufnr, i + 1))
+ has_trailing_ws = (line[-1] in bws)
+ else:
+ line = vim.eval('strtrans(getbufline({0}, {1}))'.format(
+ bufnr, i + 1))
+ has_trailing_ws = (line[-1] in bws)
+ else:
+ has_trailing_ws = (line and line[-1] in sws)
+ if has_trailing_ws:
+ break
+ if has_trailing_ws:
+ ret = [{
+ 'contents': str(i + 1),
+ 'highlight_groups': ['trailing_whitespace', 'warning'],
+ }]
+ else:
+ ret = None
+ trailing_whitespace_cache[bufnr] = (changedtick, ret)
+ return ret
+
+
+@requires_segment_info
+def tabnr(pl, segment_info, show_current=True):
+ '''Show tabpage number
+
+ :param bool show_current:
+ If False do not show current tabpage number. This is default because
+ tabnr is by default only present in tabline.
+ '''
+ try:
+ tabnr = segment_info['tabnr']
+ except KeyError:
+ return None
+ if show_current or tabnr != current_tabpage().number:
+ return str(tabnr)
+
+
+@requires_segment_info
+def bufnr(pl, segment_info, show_current=True):
+ '''Show buffer number
+
+ :param bool show_current:
+ If False do not show current window number.
+ '''
+ bufnr = segment_info['bufnr']
+ if show_current or bufnr != vim.current.buffer.number:
+ return str(bufnr)
+
+
+@requires_segment_info
+def winnr(pl, segment_info, show_current=True):
+ '''Show window number
+
+ :param bool show_current:
+ If False do not show current window number.
+ '''
+ winnr = segment_info['winnr']
+ if show_current or winnr != vim.current.window.number:
+ return str(winnr)
+
+
+csv_cache = None
+sniffer = csv.Sniffer()
+
+
+def detect_text_csv_dialect(text, display_name, header_text=None):
+ return (
+ sniffer.sniff(string(text)),
+ sniffer.has_header(string(header_text or text)) if display_name == 'auto' else display_name,
+ )
+
+
+CSV_SNIFF_LINES = 100
+CSV_PARSE_LINES = 10
+
+
+if sys.version_info < (2, 7):
+ def read_csv(l, dialect, fin=next):
+ try:
+ return fin(csv.reader(l, dialect))
+ except csv.Error as e:
+ if str(e) == 'newline inside string' and dialect.quotechar:
+ # Maybe we are inside an unfinished quoted string. Python-2.6
+ # does not handle this fine
+ return fin(csv.reader(l[:-1] + [l[-1] + dialect.quotechar]))
+ else:
+ raise
+else:
+ def read_csv(l, dialect, fin=next):
+ return fin(csv.reader(l, dialect))
+
+
+def process_csv_buffer(pl, buffer, line, col, display_name):
+ global csv_cache
+ if csv_cache is None:
+ csv_cache = register_buffer_cache(defaultdict(lambda: (None, None, None)))
+ try:
+ cur_first_line = buffer[0]
+ except UnicodeDecodeError:
+ cur_first_line = vim.eval('strtrans(getline(1))')
+ dialect, has_header, first_line = csv_cache[buffer.number]
+ if dialect is None or (cur_first_line != first_line and display_name == 'auto'):
+ try:
+ text = '\n'.join(buffer[:CSV_SNIFF_LINES])
+ except UnicodeDecodeError: # May happen in Python 3
+ text = vim.eval('join(map(getline(1, {0}), "strtrans(v:val)"), "\\n")'.format(CSV_SNIFF_LINES))
+ try:
+ dialect, has_header = detect_text_csv_dialect(text, display_name)
+ except csv.Error as e:
+ pl.warn('Failed to detect csv format: {0}', str(e))
+ # Try detecting using three lines only:
+ if line == 1:
+ rng = (0, line + 2)
+ elif line == len(buffer):
+ rng = (line - 3, line)
+ else:
+ rng = (line - 2, line + 1)
+ try:
+ dialect, has_header = detect_text_csv_dialect(
+ '\n'.join(buffer[rng[0]:rng[1]]),
+ display_name,
+ header_text='\n'.join(buffer[:4]),
+ )
+ except csv.Error as e:
+ pl.error('Failed to detect csv format: {0}', str(e))
+ return None, None
+ if len(buffer) > 2:
+ csv_cache[buffer.number] = dialect, has_header, cur_first_line
+ column_number = len(read_csv(
+ buffer[max(0, line - CSV_PARSE_LINES):line - 1] + [buffer[line - 1][:col]],
+ dialect=dialect,
+ fin=list,
+ )[-1]) or 1
+ if has_header:
+ try:
+ header = read_csv(buffer[0:1], dialect=dialect)
+ except UnicodeDecodeError:
+ header = read_csv([vim.eval('strtrans(getline(1))')], dialect=dialect)
+ column_name = header[column_number - 1]
+ else:
+ column_name = None
+ return unicode(column_number), column_name
+
+
+@requires_segment_info
+def csv_col_current(pl, segment_info, display_name='auto', name_format=' ({column_name:.15})'):
+ '''Display CSV column number and column name
+
+ Requires filetype to be set to ``csv``.
+
+ :param bool or str name:
+ May be ``True``, ``False`` and ``"auto"``. In the first case value from
+ the first raw will always be displayed. In the second case it will never
+ be displayed. In the last case ``csv.Sniffer().has_header()`` will be
+ used to detect whether current file contains header in the first column.
+ :param str name_format:
+ String used to format column name (in case ``display_name`` is set to
+ ``True`` or ``"auto"``). Accepts ``column_name`` keyword argument.
+
+ Highlight groups used: ``csv:column_number`` or ``csv``, ``csv:column_name`` or ``csv``.
+ '''
+ if vim_getbufoption(segment_info, 'filetype') != 'csv':
+ return None
+ line, col = segment_info['window'].cursor
+ column_number, column_name = process_csv_buffer(pl, segment_info['buffer'], line, col, display_name)
+ if not column_number:
+ return None
+ return [{
+ 'contents': column_number,
+ 'highlight_groups': ['csv:column_number', 'csv'],
+ }] + ([{
+ 'contents': name_format.format(column_name=column_name),
+ 'highlight_groups': ['csv:column_name', 'csv'],
+ }] if column_name else [])
+
+
+@requires_segment_info
+def tab(pl, segment_info, end=False):
+ '''Mark start of the clickable region for tabpage
+
+ :param bool end:
+ In place of starting region for the current tab end it.
+
+ No highlight groups are used (literal segment).
+ '''
+ try:
+ return [{
+ 'contents': None,
+ 'literal_contents': (0, '%{tabnr}T'.format(tabnr=('' if end else segment_info['tabnr']))),
+ }]
+ except KeyError:
+ return None
diff --git a/powerline/segments/vim/plugin/__init__.py b/powerline/segments/vim/plugin/__init__.py
new file mode 100644
index 0000000..b2b9f10
--- /dev/null
+++ b/powerline/segments/vim/plugin/__init__.py
@@ -0,0 +1,6 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+from pkgutil import extend_path
+
+
+__path__ = extend_path(__path__, __name__)
diff --git a/powerline/segments/vim/plugin/ale.py b/powerline/segments/vim/plugin/ale.py
new file mode 100644
index 0000000..4f4bdee
--- /dev/null
+++ b/powerline/segments/vim/plugin/ale.py
@@ -0,0 +1,52 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import vim_global_exists
+from powerline.theme import requires_segment_info
+
+
+@requires_segment_info
+def ale(segment_info, pl, err_format='ERR: ln {first_line} ({num}) ', warn_format='WARN: ln {first_line} ({num}) '):
+ '''Show whether ALE has found any errors or warnings
+
+ :param str err_format:
+ Format string for errors.
+
+ :param str warn_format:
+ Format string for warnings.
+
+ Highlight groups used: ``ale:warning`` or ``warning``, ``ale:error`` or ``error``.
+ '''
+ if not (vim_global_exists('ale_enabled') and int(vim.eval('g:ale_enabled'))):
+ return None
+ has_errors = int(vim.eval('ale#statusline#Count(' + str(segment_info['bufnr']) + ').total'))
+ if not has_errors:
+ return
+ error = None
+ warning = None
+ errors_count = 0
+ warnings_count = 0
+ for issue in vim.eval('ale#engine#GetLoclist(' + str(segment_info['bufnr']) + ')'):
+ if issue['type'] == 'E':
+ error = error or issue
+ errors_count += 1
+ elif issue['type'] == 'W':
+ warning = warning or issue
+ warnings_count += 1
+ segments = []
+ if error:
+ segments.append({
+ 'contents': err_format.format(first_line=error['lnum'], num=errors_count),
+ 'highlight_groups': ['ale:error', 'error'],
+ })
+ if warning:
+ segments.append({
+ 'contents': warn_format.format(first_line=warning['lnum'], num=warnings_count),
+ 'highlight_groups': ['ale:warning', 'warning'],
+ })
+ return segments
diff --git a/powerline/segments/vim/plugin/capslock.py b/powerline/segments/vim/plugin/capslock.py
new file mode 100644
index 0000000..d2c474d
--- /dev/null
+++ b/powerline/segments/vim/plugin/capslock.py
@@ -0,0 +1,30 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import vim_func_exists
+from powerline.theme import requires_segment_info
+
+
+@requires_segment_info
+def capslock_indicator(pl, segment_info, text='CAPS'):
+ '''Shows the indicator if tpope/vim-capslock plugin is enabled
+
+ .. note::
+ In the current state plugin automatically disables itself when leaving
+ insert mode. So trying to use this segment not in insert or replace
+ modes is useless.
+
+ :param str text:
+ String to show when software capslock presented by this plugin is
+ active.
+ '''
+ if not vim_func_exists('CapsLockStatusline'):
+ return None
+ # CapsLockStatusline() function returns an empty string when plugin is
+ # disabled. If it is not then string is non-empty.
+ return text if vim.eval('CapsLockStatusline()') else None
diff --git a/powerline/segments/vim/plugin/coc.py b/powerline/segments/vim/plugin/coc.py
new file mode 100644
index 0000000..290faec
--- /dev/null
+++ b/powerline/segments/vim/plugin/coc.py
@@ -0,0 +1,51 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import vim_command_exists
+from powerline.theme import requires_segment_info
+
+# coc_status's format: E1 W2
+def parse_coc_status(coc_status):
+ # type(coc_status) is tuple
+ errors_count = 0
+ warnings_count = 0
+ if len(coc_status) <= 0:
+ return errors_count, warnings_count
+ status_str = coc_status[0]
+ if len(status_str) <= 0:
+ return errors_count, warnings_count
+ status_list = status_str.split(' ')
+ for item in status_list:
+ if len(item) > 0 and item[0] == 'E':
+ errors_count = int(item[1:])
+ if len(item) > 0 and item[0] == 'W':
+ warnings_count = int(item[1:])
+ return errors_count, warnings_count
+
+@requires_segment_info
+def coc(segment_info, pl):
+ '''Show whether coc.nvim has found any errors or warnings
+
+ Highlight groups used: ``coc:warning`` or ``warning``, ``coc:error`` or ``error``.
+ '''
+ segments = []
+ if not vim_command_exists('CocCommand'):
+ return segments
+ coc_status = vim.eval('coc#status()'),
+ errors_count, warnings_count = parse_coc_status(coc_status)
+ if errors_count > 0:
+ segments.append({
+ 'contents': 'E:' + str(errors_count),
+ 'highlight_groups': ['coc:error', 'error'],
+ })
+ if warnings_count > 0:
+ segments.append({
+ 'contents': 'W:' + str(warnings_count),
+ 'highlight_groups': ['coc:warning', 'warning'],
+ })
+ return segments
diff --git a/powerline/segments/vim/plugin/commandt.py b/powerline/segments/vim/plugin/commandt.py
new file mode 100644
index 0000000..7e5262e
--- /dev/null
+++ b/powerline/segments/vim/plugin/commandt.py
@@ -0,0 +1,97 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import create_ruby_dpowerline
+
+
+def initialize():
+ global initialized
+ if initialized:
+ return
+ initialized = True
+ create_ruby_dpowerline()
+ vim.command((
+ # When using :execute (vim.command uses the same code) one should not
+ # use << EOF.
+ '''
+ ruby
+ if (not ($command_t.respond_to? 'active_finder'))
+ def $command_t.active_finder
+ @active_finder and @active_finder.class.name or ''
+ end
+ end
+ if (not ($command_t.respond_to? 'path'))
+ def $command_t.path
+ @path or ''
+ end
+ end
+ def $powerline.commandt_set_active_finder
+ ::VIM::command "let g:powerline_commandt_reply = '#{$command_t.active_finder}'"
+ end
+ def $powerline.commandt_set_path
+ ::VIM::command "let g:powerline_commandt_reply = '#{($command_t.path or '').gsub(/'/, "''")}'"
+ end
+ '''
+ ))
+
+
+initialized = False
+
+
+def finder(pl):
+ '''Display Command-T finder name
+
+ Requires $command_t.active_finder and methods (code above may monkey-patch
+ $command_t to add them). All Command-T finders have ``CommandT::`` module
+ prefix, but it is stripped out (actually, any ``CommandT::`` substring will
+ be stripped out).
+
+ Highlight groups used: ``commandt:finder``.
+ '''
+ initialize()
+ vim.command('ruby $powerline.commandt_set_active_finder')
+ return [{
+ 'highlight_groups': ['commandt:finder'],
+ 'contents': vim.eval('g:powerline_commandt_reply').replace('CommandT::', '').replace('Finder::', '')
+ }]
+
+
+FINDERS_WITHOUT_PATH = set((
+ 'CommandT::MRUBufferFinder',
+ 'CommandT::BufferFinder',
+ 'CommandT::TagFinder',
+ 'CommandT::JumpFinder',
+ 'CommandT::Finder::MRUBufferFinder',
+ 'CommandT::Finder::BufferFinder',
+ 'CommandT::Finder::TagFinder',
+ 'CommandT::Finder::JumpFinder',
+))
+
+
+def path(pl):
+ '''Display path used by Command-T
+
+ Requires $command_t.active_finder and .path methods (code above may
+ monkey-patch $command_t to add them).
+
+ $command_t.active_finder is required in order to omit displaying path for
+ finders ``MRUBufferFinder``, ``BufferFinder``, ``TagFinder`` and
+ ``JumpFinder`` (pretty much any finder, except ``FileFinder``).
+
+ Highlight groups used: ``commandt:path``.
+ '''
+ initialize()
+ vim.command('ruby $powerline.commandt_set_active_finder')
+ finder = vim.eval('g:powerline_commandt_reply')
+ if finder in FINDERS_WITHOUT_PATH:
+ return None
+ vim.command('ruby $powerline.commandt_set_path')
+ return [{
+ 'highlight_groups': ['commandt:path'],
+ 'contents': vim.eval('g:powerline_commandt_reply')
+ }]
diff --git a/powerline/segments/vim/plugin/nerdtree.py b/powerline/segments/vim/plugin/nerdtree.py
new file mode 100644
index 0000000..f11be14
--- /dev/null
+++ b/powerline/segments/vim/plugin/nerdtree.py
@@ -0,0 +1,25 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import bufvar_exists
+from powerline.segments.vim import window_cached
+
+
+@window_cached
+def nerdtree(pl):
+ '''Return directory that is shown by the current buffer.
+
+ Highlight groups used: ``nerdtree:path`` or ``file_name``.
+ '''
+ if not bufvar_exists(None, 'NERDTreeRoot'):
+ return None
+ path_str = vim.eval('getbufvar("%", "NERDTreeRoot").path.str()')
+ return [{
+ 'contents': path_str,
+ 'highlight_groups': ['nerdtree:path', 'file_name'],
+ }]
diff --git a/powerline/segments/vim/plugin/syntastic.py b/powerline/segments/vim/plugin/syntastic.py
new file mode 100644
index 0000000..5bef3c7
--- /dev/null
+++ b/powerline/segments/vim/plugin/syntastic.py
@@ -0,0 +1,43 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.segments.vim import window_cached
+from powerline.bindings.vim import vim_global_exists
+
+
+@window_cached
+def syntastic(pl, err_format='ERR:  {first_line} ({num}) ', warn_format='WARN:  {first_line} ({num}) '):
+ '''Show whether syntastic has found any errors or warnings
+
+ :param str err_format:
+ Format string for errors.
+
+ :param str warn_format:
+ Format string for warnings.
+
+ Highlight groups used: ``syntastic:warning`` or ``warning``, ``syntastic:error`` or ``error``.
+ '''
+ if not vim_global_exists('SyntasticLoclist'):
+ return None
+ has_errors = int(vim.eval('g:SyntasticLoclist.current().hasErrorsOrWarningsToDisplay()'))
+ if not has_errors:
+ return
+ errors = vim.eval('g:SyntasticLoclist.current().errors()')
+ warnings = vim.eval('g:SyntasticLoclist.current().warnings()')
+ segments = []
+ if errors:
+ segments.append({
+ 'contents': err_format.format(first_line=errors[0]['lnum'], num=len(errors)),
+ 'highlight_groups': ['syntastic:error', 'error'],
+ })
+ if warnings:
+ segments.append({
+ 'contents': warn_format.format(first_line=warnings[0]['lnum'], num=len(warnings)),
+ 'highlight_groups': ['syntastic:warning', 'warning'],
+ })
+ return segments
diff --git a/powerline/segments/vim/plugin/tagbar.py b/powerline/segments/vim/plugin/tagbar.py
new file mode 100644
index 0000000..e683758
--- /dev/null
+++ b/powerline/segments/vim/plugin/tagbar.py
@@ -0,0 +1,51 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import vim_command_exists, vim_get_autoload_func
+from powerline.theme import requires_segment_info
+
+
+currenttag = None
+tag_cache = {}
+
+
+@requires_segment_info
+def current_tag(segment_info, pl, flags='s'):
+ '''Return tag that is near the cursor.
+
+ :param str flags:
+ Specifies additional properties of the displayed tag. Supported values:
+
+ * s - display complete signature
+ * f - display the full hierarchy of the tag
+ * p - display the raw prototype
+
+ More info in the `official documentation`_ (search for
+ “tagbar#currenttag”).
+
+ .. _`official documentation`: https://github.com/majutsushi/tagbar/blob/master/doc/tagbar.txt
+ '''
+ global currenttag
+ global tag_cache
+ window_id = segment_info['window_id']
+ if segment_info['mode'] == 'nc':
+ return tag_cache.get(window_id, (None,))[-1]
+ if not currenttag:
+ if vim_command_exists('Tagbar'):
+ currenttag = vim_get_autoload_func('tagbar#currenttag')
+ if not currenttag:
+ return None
+ else:
+ return None
+ prev_key, r = tag_cache.get(window_id, (None, None))
+ key = (int(vim.eval('b:changedtick')), segment_info['window'].cursor[0])
+ if prev_key and key == prev_key:
+ return r
+ r = currenttag('%s', '', flags)
+ tag_cache[window_id] = (key, r)
+ return r
diff --git a/powerline/selectors/__init__.py b/powerline/selectors/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/powerline/selectors/__init__.py
diff --git a/powerline/selectors/vim.py b/powerline/selectors/vim.py
new file mode 100644
index 0000000..d111de9
--- /dev/null
+++ b/powerline/selectors/vim.py
@@ -0,0 +1,10 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline.bindings.vim import list_tabpages
+
+
+def single_tab(pl, segment_info, mode):
+ '''Returns True if Vim has only one tab opened
+ '''
+ return len(list_tabpages()) == 1
diff --git a/powerline/shell.py b/powerline/shell.py
new file mode 100644
index 0000000..e10692c
--- /dev/null
+++ b/powerline/shell.py
@@ -0,0 +1,38 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+from powerline import Powerline
+from powerline.lib.dict import mergedicts
+
+
+class ShellPowerline(Powerline):
+ def init(self, args, **kwargs):
+ self.args = args
+ super(ShellPowerline, self).init(args.ext[0], args.renderer_module, **kwargs)
+
+ def load_main_config(self):
+ r = super(ShellPowerline, self).load_main_config()
+ if self.args.config_override:
+ mergedicts(r, self.args.config_override)
+ return r
+
+ def load_theme_config(self, name):
+ r = super(ShellPowerline, self).load_theme_config(name)
+ if self.args.theme_override and name in self.args.theme_override:
+ mergedicts(r, self.args.theme_override[name])
+ return r
+
+ def get_config_paths(self):
+ return self.args.config_path or super(ShellPowerline, self).get_config_paths()
+
+ def get_local_themes(self, local_themes):
+ if not local_themes:
+ return {}
+
+ return dict((
+ (key, {'config': self.load_theme_config(val)})
+ for key, val in local_themes.items()
+ ))
+
+ def do_setup(self, obj):
+ obj.powerline = self
diff --git a/powerline/theme.py b/powerline/theme.py
new file mode 100644
index 0000000..b3a23a1
--- /dev/null
+++ b/powerline/theme.py
@@ -0,0 +1,182 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import itertools
+
+from powerline.segment import gen_segment_getter, process_segment, get_fallback_segment
+from powerline.lib.unicode import u, safe_unicode
+
+
+def requires_segment_info(func):
+ func.powerline_requires_segment_info = True
+ return func
+
+
+def requires_filesystem_watcher(func):
+ func.powerline_requires_filesystem_watcher = True
+ return func
+
+
+def new_empty_segment_line():
+ return {
+ 'left': [],
+ 'right': []
+ }
+
+
+def add_spaces_left(pl, amount, segment):
+ return (' ' * amount) + segment['contents']
+
+
+def add_spaces_right(pl, amount, segment):
+ return segment['contents'] + (' ' * amount)
+
+
+def add_spaces_center(pl, amount, segment):
+ amount, remainder = divmod(amount, 2)
+ return (' ' * (amount + remainder)) + segment['contents'] + (' ' * amount)
+
+
+expand_functions = {
+ 'l': add_spaces_right,
+ 'r': add_spaces_left,
+ 'c': add_spaces_center,
+}
+
+
+class Theme(object):
+ def __init__(self,
+ ext,
+ theme_config,
+ common_config,
+ pl,
+ get_module_attr,
+ top_theme,
+ colorscheme,
+ main_theme_config=None,
+ run_once=False,
+ shutdown_event=None):
+ self.colorscheme = colorscheme
+ 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()
+ ))
+ try:
+ self.cursor_space_multiplier = 1 - (theme_config['cursor_space'] / 100)
+ except KeyError:
+ self.cursor_space_multiplier = None
+ self.cursor_columns = theme_config.get('cursor_columns')
+ self.spaces = theme_config['spaces']
+ self.outer_padding = int(theme_config.get('outer_padding', 1))
+ self.segments = []
+ self.EMPTY_SEGMENT = {
+ 'contents': None,
+ 'highlight': {'fg': False, 'bg': False, 'attrs': 0}
+ }
+ self.pl = pl
+ theme_configs = [theme_config]
+ if main_theme_config:
+ theme_configs.append(main_theme_config)
+ get_segment = gen_segment_getter(
+ pl,
+ ext,
+ common_config,
+ theme_configs,
+ theme_config.get('default_module'),
+ get_module_attr,
+ top_theme
+ )
+ for segdict in itertools.chain((theme_config['segments'],),
+ theme_config['segments'].get('above', ())):
+ self.segments.append(new_empty_segment_line())
+ for side in ['left', 'right']:
+ for segment in segdict.get(side, []):
+ segment = get_segment(segment, side)
+ if segment:
+ if not run_once:
+ if segment['startup']:
+ try:
+ segment['startup'](pl, shutdown_event)
+ except Exception as e:
+ pl.error('Exception during {0} startup: {1}', segment['name'], str(e))
+ continue
+ self.segments[-1][side].append(segment)
+
+ def shutdown(self):
+ for line in self.segments:
+ for segments in line.values():
+ for segment in segments:
+ try:
+ segment['shutdown']()
+ except TypeError:
+ pass
+
+ def get_divider(self, side='left', type='soft'):
+ '''Return segment divider.'''
+ return self.dividers[side][type]
+
+ def get_spaces(self):
+ return self.spaces
+
+ def get_line_number(self):
+ return len(self.segments)
+
+ def get_segments(self, side=None, line=0, segment_info=None, mode=None):
+ '''Return all segments.
+
+ Function segments are called, and all segments get their before/after
+ and ljust/rjust properties applied.
+
+ :param int line:
+ Line number for which segments should be obtained. Is counted from
+ zero (botmost line).
+ '''
+ for side in [side] if side else ['left', 'right']:
+ parsed_segments = []
+ for segment in self.segments[line][side]:
+ if segment['display_condition'](self.pl, segment_info, mode):
+ process_segment(
+ self.pl,
+ side,
+ segment_info,
+ parsed_segments,
+ segment,
+ mode,
+ self.colorscheme,
+ )
+ for segment in parsed_segments:
+ self.pl.prefix = segment['name']
+ try:
+ width = segment['width']
+ align = segment['align']
+ if width == 'auto' and segment['expand'] is None:
+ segment['expand'] = expand_functions.get(align)
+ if segment['expand'] is None:
+ self.pl.error('Align argument must be “r”, “l” or “c”, not “{0}”', align)
+
+ try:
+ segment['contents'] = segment['before'] + u(
+ segment['contents'] if segment['contents'] is not None else ''
+ ) + segment['after']
+ except Exception as e:
+ self.pl.exception('Failed to compute segment contents: {0}', str(e))
+ segment['contents'] = safe_unicode(segment.get('contents'))
+ # Align segment contents
+ if segment['width'] and segment['width'] != 'auto':
+ if segment['align'] == 'l':
+ segment['contents'] = segment['contents'].ljust(segment['width'])
+ elif segment['align'] == 'r':
+ segment['contents'] = segment['contents'].rjust(segment['width'])
+ elif segment['align'] == 'c':
+ segment['contents'] = segment['contents'].center(segment['width'])
+ # We need to yield a copy of the segment, or else mode-dependent
+ # segment contents can’t be cached correctly e.g. when caching
+ # non-current window contents for vim statuslines
+ yield segment.copy()
+ except Exception as e:
+ self.pl.exception('Failed to compute segment: {0}', str(e))
+ fallback = get_fallback_segment()
+ fallback.update(side=side)
+ yield fallback
diff --git a/powerline/version.py b/powerline/version.py
new file mode 100644
index 0000000..f380c80
--- /dev/null
+++ b/powerline/version.py
@@ -0,0 +1,7 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+__version__ = "2.8.3"
+
+def get_version():
+ return __version__
diff --git a/powerline/vim.py b/powerline/vim.py
new file mode 100644
index 0000000..2638c91
--- /dev/null
+++ b/powerline/vim.py
@@ -0,0 +1,359 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import sys
+import json
+import logging
+
+from itertools import count
+
+try:
+ import vim
+except ImportError:
+ vim = object()
+
+from powerline.bindings.vim import vim_get_func, vim_getvar, get_vim_encoding, python_to_vim
+from powerline import Powerline, FailedUnicode, finish_common_config
+from powerline.lib.dict import mergedicts
+from powerline.lib.unicode import u
+
+
+def _override_from(config, override_varname, key=None):
+ try:
+ overrides = vim_getvar(override_varname)
+ except KeyError:
+ return config
+ if key is not None:
+ try:
+ overrides = overrides[key]
+ except KeyError:
+ return config
+ mergedicts(config, overrides)
+ return config
+
+
+class VimVarHandler(logging.Handler, object):
+ '''Vim-specific handler which emits messages to Vim global variables
+
+ :param str varname:
+ Variable where
+ '''
+ def __init__(self, varname):
+ super(VimVarHandler, self).__init__()
+ utf_varname = u(varname)
+ self.vim_varname = utf_varname.encode('ascii')
+ vim.command('unlet! g:' + utf_varname)
+ vim.command('let g:' + utf_varname + ' = []')
+
+ def emit(self, record):
+ message = u(record.message)
+ if record.exc_text:
+ message += '\n' + u(record.exc_text)
+ vim.eval(b'add(g:' + self.vim_varname + b', ' + python_to_vim(message) + b')')
+
+
+class VimPowerline(Powerline):
+ def init(self, pyeval='PowerlinePyeval', **kwargs):
+ super(VimPowerline, self).init('vim', **kwargs)
+ self.last_window_id = 1
+ self.pyeval = pyeval
+ self.construct_window_statusline = self.create_window_statusline_constructor()
+ if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))):
+ self.win_idx = self.new_win_idx
+ else:
+ self.win_idx = self.old_win_idx
+ self._vim_getwinvar = vim_get_func('getwinvar', 'bytes')
+ self._vim_setwinvar = vim_get_func('setwinvar')
+
+ if sys.version_info < (3,):
+ def create_window_statusline_constructor(self):
+ window_statusline = b'%!' + str(self.pyeval) + b'(\'powerline.statusline({0})\')'
+ return window_statusline.format
+ else:
+ def create_window_statusline_constructor(self):
+ startstr = b'%!' + self.pyeval.encode('ascii') + b'(\'powerline.statusline('
+ endstr = b')\')'
+ return lambda idx: (
+ startstr + str(idx).encode('ascii') + endstr
+ )
+
+ create_window_statusline_constructor.__doc__ = (
+ '''Create function which returns &l:stl value being given window index
+
+ Created function must return :py:class:`bytes` instance because this is
+ what ``window.options['statusline']`` returns (``window`` is
+ :py:class:`vim.Window` instance).
+
+ :return:
+ Function with type ``int → bytes``.
+ '''
+ )
+
+ default_log_stream = sys.stdout
+
+ def add_local_theme(self, key, config):
+ '''Add local themes at runtime (during vim session).
+
+ :param str key:
+ Matcher name (in format ``{matcher_module}.{module_attribute}`` or
+ ``{module_attribute}`` if ``{matcher_module}`` is
+ ``powerline.matchers.vim``). Function pointed by
+ ``{module_attribute}`` should be hashable and accept a dictionary
+ with information about current buffer and return boolean value
+ indicating whether current window matched conditions. See also
+ :ref:`local_themes key description <config-ext-local_themes>`.
+
+ :param dict config:
+ :ref:`Theme <config-themes>` dictionary.
+
+ :return:
+ ``True`` if theme was added successfully and ``False`` if theme with
+ the same matcher already exists.
+ '''
+ self.update_renderer()
+ matcher = self.get_matcher(key)
+ theme_config = {}
+ for cfg_path in self.theme_levels:
+ try:
+ lvl_config = self.load_config(cfg_path, 'theme')
+ except IOError:
+ pass
+ else:
+ mergedicts(theme_config, lvl_config)
+ mergedicts(theme_config, config)
+ try:
+ self.renderer.add_local_theme(matcher, {'config': theme_config})
+ except KeyError:
+ return False
+ else:
+ # Hack for local themes support: when reloading modules it is not
+ # guaranteed that .add_local_theme will be called once again, so
+ # this function arguments will be saved here for calling from
+ # .do_setup().
+ self.setup_kwargs.setdefault('_local_themes', []).append((key, config))
+ return True
+
+ get_encoding = staticmethod(get_vim_encoding)
+
+ def load_main_config(self):
+ main_config = _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides')
+ try:
+ use_var_handler = bool(int(vim_getvar('powerline_use_var_handler')))
+ except KeyError:
+ use_var_handler = False
+ if use_var_handler:
+ main_config.setdefault('common', {})
+ main_config['common'] = finish_common_config(self.get_encoding(), main_config['common'])
+ main_config['common']['log_file'].append(['powerline.vim.VimVarHandler', [['powerline_log_messages']]])
+ return main_config
+
+ def load_theme_config(self, name):
+ return _override_from(
+ super(VimPowerline, self).load_theme_config(name),
+ 'powerline_theme_overrides',
+ name
+ )
+
+ def get_local_themes(self, local_themes):
+ if not local_themes:
+ return {}
+
+ return dict((
+ (matcher, {'config': self.load_theme_config(val)})
+ for matcher, key, val in (
+ (
+ (None if k == '__tabline__' else self.get_matcher(k)),
+ k,
+ v
+ )
+ for k, v in local_themes.items()
+ ) if (
+ matcher or
+ key == '__tabline__'
+ )
+ ))
+
+ def get_matcher(self, match_name):
+ match_module, separator, match_function = match_name.rpartition('.')
+ if not separator:
+ match_module = 'powerline.matchers.{0}'.format(self.ext)
+ match_function = match_name
+ return self.get_module_attr(match_module, match_function, prefix='matcher_generator')
+
+ def get_config_paths(self):
+ try:
+ return vim_getvar('powerline_config_paths')
+ except KeyError:
+ return super(VimPowerline, self).get_config_paths()
+
+ def do_setup(self, pyeval=None, pycmd=None, can_replace_pyeval=True, _local_themes=()):
+ import __main__
+ if not pyeval:
+ pyeval = 'pyeval' if sys.version_info < (3,) else 'py3eval'
+ can_replace_pyeval = True
+ if not pycmd:
+ pycmd = get_default_pycmd()
+
+ set_pycmd(pycmd)
+
+ # pyeval() and vim.bindeval were both introduced in one patch
+ if (not hasattr(vim, 'bindeval') and can_replace_pyeval) or pyeval == 'PowerlinePyeval':
+ vim.command(('''
+ function! PowerlinePyeval(e)
+ {pycmd} powerline.do_pyeval()
+ endfunction
+ ''').format(pycmd=pycmd))
+ pyeval = 'PowerlinePyeval'
+
+ self.pyeval = pyeval
+ self.construct_window_statusline = self.create_window_statusline_constructor()
+
+ self.update_renderer()
+ __main__.powerline = self
+
+ try:
+ if (
+ bool(int(vim.eval('has(\'gui_running\') && argc() == 0')))
+ and not vim.current.buffer.name
+ and len(vim.windows) == 1
+ ):
+ # Hack to show startup screen. Problems in GUI:
+ # - Defining local value of &statusline option while computing
+ # global value purges startup screen.
+ # - Defining highlight group while computing statusline purges
+ # startup screen.
+ # This hack removes the “while computing statusline” part: both
+ # things are defined, but they are defined right now.
+ #
+ # The above condition disables this hack if no GUI is running,
+ # Vim did not open any files and there is only one window.
+ # Without GUI everything works, in other cases startup screen is
+ # not shown.
+ self.new_window()
+ except UnicodeDecodeError:
+ # vim.current.buffer.name may raise UnicodeDecodeError when using
+ # Python-3*. Fortunately, this means that current buffer is not
+ # empty buffer, so the above condition should be False.
+ pass
+
+ # Cannot have this in one line due to weird newline handling (in :execute
+ # context newline is considered part of the command in just the same cases
+ # when bar is considered part of the command (unless defining function
+ # inside :execute)). vim.command is :execute equivalent regarding this case.
+ vim.command('augroup Powerline')
+ vim.command(' autocmd! ColorScheme * :{pycmd} powerline.reset_highlight()'.format(pycmd=pycmd))
+ vim.command(' autocmd! VimLeavePre * :{pycmd} powerline.shutdown()'.format(pycmd=pycmd))
+ vim.command('augroup END')
+
+ # Hack for local themes support after reloading.
+ for args in _local_themes:
+ self.add_local_theme(*args)
+
+ def reset_highlight(self):
+ try:
+ self.renderer.reset_highlight()
+ except AttributeError:
+ # Renderer object appears only after first `.render()` call. Thus if
+ # ColorScheme event happens before statusline is drawn for the first
+ # time AttributeError will be thrown for the self.renderer. It is
+ # fine to ignore it: no renderer == no colors to reset == no need to
+ # do anything.
+ pass
+
+ def new_win_idx(self, window_id):
+ r = None
+
+ for window in vim.windows:
+ try:
+ curwindow_id = window.vars['powerline_window_id']
+ if r is not None and curwindow_id == window_id:
+ raise KeyError
+ except KeyError:
+ curwindow_id = self.last_window_id
+ self.last_window_id += 1
+ window.vars['powerline_window_id'] = curwindow_id
+ statusline = self.construct_window_statusline(curwindow_id)
+ if window.options['statusline'] != statusline:
+ window.options['statusline'] = statusline
+ if curwindow_id == window_id if window_id else window is vim.current.window:
+ r = (window, curwindow_id, window.number)
+ return r
+
+ def old_win_idx(self, window_id):
+ r = None
+ for winnr, window in zip(count(1), vim.windows):
+ curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id')
+ if curwindow_id and not (r is not None and curwindow_id == window_id):
+ curwindow_id = int(curwindow_id)
+ else:
+ curwindow_id = self.last_window_id
+ self.last_window_id += 1
+ self._vim_setwinvar(winnr, 'powerline_window_id', curwindow_id)
+ statusline = self.construct_window_statusline(curwindow_id)
+ if self._vim_getwinvar(winnr, '&statusline') != statusline:
+ self._vim_setwinvar(winnr, '&statusline', statusline)
+ if curwindow_id == window_id if window_id else window is vim.current.window:
+ r = (window, curwindow_id, winnr)
+ return r
+
+ def statusline(self, window_id):
+ window, window_id, winnr = self.win_idx(window_id) or (None, None, None)
+ if not window:
+ return FailedUnicode('No window {0}'.format(window_id))
+ return self.render(window, window_id, winnr)
+
+ def tabline(self):
+ r = self.win_idx(None)
+
+ if r:
+ return self.render(*r, is_tabline=True)
+ else:
+ win = vim.current.window
+ r = (
+ win,
+ win.vars.get('powerline_window_id', self.last_window_id),
+ win.number,
+ )
+ return self.render(*r, is_tabline=True)
+
+ def new_window(self):
+ return self.render(*self.win_idx(None))
+
+ @staticmethod
+ def do_pyeval():
+ '''Evaluate python string passed to PowerlinePyeval
+
+ Is here to reduce the number of requirements to __main__ globals to just
+ one powerline object (previously it required as well vim and json).
+ '''
+ import __main__
+ vim.command('return ' + json.dumps(eval(vim.eval('a:e'), __main__.__dict__)))
+
+ def setup_components(self, components):
+ if components is None:
+ components = ('statusline', 'tabline')
+ if 'statusline' in components:
+ # Is immediately changed after new_window function is run. Good for
+ # global value.
+ vim.command('set statusline=%!{pyeval}(\'powerline.new_window()\')'.format(
+ pyeval=self.pyeval))
+ if 'tabline' in components:
+ vim.command('set tabline=%!{pyeval}(\'powerline.tabline()\')'.format(
+ pyeval=self.pyeval))
+
+
+pycmd = None
+
+
+def set_pycmd(new_pycmd):
+ global pycmd
+ pycmd = new_pycmd
+
+
+def get_default_pycmd():
+ return 'python' if sys.version_info < (3,) else 'python3'
+
+
+def setup(*args, **kwargs):
+ powerline = VimPowerline()
+ return powerline.setup(*args, **kwargs)